javainheritancelambdaoption-typebounded-wildcard

Bad return type in lambda expression when using Java's Optional.or() with subclasses


I am trying to use Optional.or to get an object of subclass A or, if empty, an object of subclass B:

interface Node {}
class InnerNode implements Node {}
class LeafNode implements Node {}
Optional<InnerNode> createInnerNode() {}
Optional<LeafNode> createLeafNode() {}

Optional<Node> node = createInnerNode().or(() -> createLeafNode());

When doing this, I get the following compiler error:

Bad return type in lambda expression: Optional<LeafNode> cannot be converted to Optional<? extends InnerNode>

If I instead use wildcards to explicitly tell the compiler that the Optionals contain an object extending from Node:

Optional<? extends Node> optionalInner = createInnerNode();
Optional<? extends Node> optionalLeaf = createLeafNode();
Optional<Node> node = optionalInner.or(() -> optionalLeaf);

I get the following compiler error:

Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>

The only way I found to make this work, is using an Optional.empty in front:

Optional<Node> node = Optional.<Node>empty() // requires generic argument
    .or(() -> createInnerNode())
    .or(() -> createLeafNode());

But for me, this is confusing to read. Is it somehow possible to instruct the compiler to allow the statement optionalInner.or(() -> optionalLeaf)? Or are there other alternatives to make this work?


Solution

  • The workaround you provided is a pretty simple answer, but you can convert an Optional<InnerNode> to an Optional<Node> trivially with map if you want to avoid a placeholder Optional.

    createInnerNode.<Node>map(Function.identity()).or(() -> createLeafNode());
    

    In general, this is also how you conceptually turn a List<Dog> to a List<Animal> or any generic collection to a different generic collection in java as well. It's somewhat annoying, but wildcards don't cut it because you want both read/write on the transformed collection.

    The signature of or should tell you why your workaround works

    Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
    

    notice how it's already using the ? extends T in the supplier. Since the first empty Optional's T is Node, the or function can accept a supplier that returns

    Optional<? extends Node>
    

    and since both the innernode and leafnode are an Optional<? extends Node>, this works.

    So the problem is that you need an Optional<Node> at some point.

    I don't think you can avoid doing something since an Optional<InnerNode> never type matches Optional<Node> for reasons listed in other answers (and other questions on stack overflow).

    Reading the other answer and trying

    Optional<? extends Node> node = createInnerNode();
    

    means that the T is a

    ? extends Node
    

    which makes or seem to want doubly nested captures? I can't stop getting

    Bad return type in lambda expression: Optional<capture of ? extends Node> cannot be converted to Optional<? extends capture of ? extends Node>
    

    when trying basically any cast with the following code

    Optional<? extends Node> node = Node.createInnerNode();
    node.or(() -> (Optional<? extends Node>) null);