Optional Fun with Java 8

JavaLambda.pngSo, in my primary personal project, I've found a need for a data structure whose implementation is much like a linked list. Having run into the new (as of Java 8) Optional class by way of Stream this seemed like a wonderful excuse to look into it further. Optional is simply a class wrapping a reference that may or may not be there, for cases in which that references absence is not an error. It leverages Java lambdas to allow for code that doesn't care so much whether it is there or not, or is at least minimally obtrusive in that regard.

Let's see a degenerate version of my data structure:

public class RelationElement {
    private String name;
    private Optional next=Optional.empty();
     
    public RelationElement(String name) {
        this.name = name;
    }
    public RelationElement(String name, RelationElement next) {
        this.name = name;
        this.next = Optional.of(next);
    }
     
    public String toString() {
        return name;
    }
     
    public int getCount() {
        return next.map(node -> node.getCount() + 1).orElse(1);
    }
     
    public void forEach(Consumer visitor) {
        visitor.accept(this);
        next.ifPresent(n -> n.forEach(visitor));
    }
    public static void main(String[] args) {
        RelationElement node2 = new RelationElement("node2");
        RelationElement node1 = new RelationElement("node1", node2);
         
        int count = node1.getCount();
         
        assert(count==2);
         
        node1.forEach(node -> System.out.println(node));
    }
}

 

So, in this degenerate case, RelationElement is just a linked list node with a 'name' data element, not even providing a mutator or an accessor for its 'next' element. All you can do is visit all nodes (using 'forEach' in keeping with Java usage) or get a count of nodes in the list. Those two methods, though, show enough of the Optional<T> class to get a flavor for it.

'forEach' does a simple pre-order traversal, performing whatever the passed-in Consumer does (Consumer<T> being a functional interface allowing a lambda to be passed). It uses the 'ifPresent' method of Optional to call itself recursively if and only if the 'next' Optional has a value. Once we wind down the list to the end, recursion will stop and we'll return our way up the stack.

'count' is a bit more interesting, since it uses 'map' to recursively return the count of elements after the current one plus 1 for the current, orElse it returns 1 (we have to count 'this' element, even if it's the last element of the list). The thing that makes this work is the behavior of 'map' in this class: when called on an instance of Optional<T>, it returns an Optional<U>, where U is the return type of the lambda passed in. That Optional<U> is empty if the Optional<T> is empty. In this case, T is RelationElement and U is Integer (because of auto-boxing of the int), and when we get to the tail of the list the 'next' Optional is empty so map returns an empty Optional<Integer> without calling the lambda (that's where the 'orElse' returns '1' counting the last element of the list), causing the whole recursive mess to unwind.

I think that 'count' example shows the utility of Optional - it's like a programmable semicolon, almost, allowing us to hide the null-checking behavior that isn't really very interesting so that we can better see the code that is interesting.

Hmm, where have I seen the phrase 'programmable semicolon' before? Yep, Optional is what functional folks call a Monad. I'll save you the customary ill-worded and poorly understood explanation of the 'abstract nonsense' from whence the monad came and give you a couple of links: Monads are elephants is a readable and sensible introduction to the beasts. What's wrong in Java 8, part IV is a good discussion of Optional's shortcomings from a functional point of view (from my point of view the worst of these is lack of integration with the non-streams parts of the Java standard library - this is what will prove annoying at best. Lack of Try or Either monads is correctable [see Better Java Monads] albeit with the same integration issues).

Related Articles