简体   繁体   中英

Java - Bounded generic as input to bounded generic method

Given a generic Type Result<T> with the following partial implementation

public class Result<T> {

    /* fields, ctor, etc... */

    public Result<T> mergeWith(Result<? extends T> other) {
        /* fiddle with this and other (acting as producer) and return */
    }

    /* other methods */

}

and the use of it in a Visitor...

Interface:

public interface Visitor<T> {

    T visitFile(FileContext ctx);

    T aggregate(T left, T right);

    /* ... */

}

Implementation:

public class SomeVisitor implements Visitor<Result<? extends Node>> {

    // this works fine, as I can return subtypes of 'Node'
    @Override
    public Result<FileNode> visitFile() { }

    /* ... */

    @Override
    public Result<? extends Node> aggregate(Result<? extends Node> left,
                                               Result<? extends Node> right) {
        // This line completely blows up.
        Result<? extends Node> tempResult = left.mergeWith(right);
        // Expected type: 'Option<? extends capture of ? extends Node>'
        // Have type:     'Option<                     ? extends Node>'
    }

Callsite of SomeVisitor :

FileContext fileCtx = someOtherService.acquireFileContext();
SomeVisitor visitor = new SomeVisitor();
Result<FileNode> fileNodeResult = visitor.visitFile(fileCtx);
// process the result

The above example fails with the given type-mismatch error messages.

I have already tried to change the signature of .mergeWith to accept the much narrower Result<T> instead of a Result<? extends T> Result<? extends T> . This leads to an expected type of Result<capture of ? extends Node> Result<capture of ? extends Node> in this example. And breaking it in other places, where <? extends T> <? extends T> is the correct generic type, since other is a producer in this case.

The only solution that actually works, is casting down both left and right to Result<Node> , merge them and then return:

@SuppressWarnings("unchecked")
Result<Node> tempLeft   = (Result<Node>) left;
@SuppressWarnings("unchecked")
Result<Node> tempRight  = (Result<Node>) right;
Result<Node> tempResult = tempLeft.mergeWith(tempRight);

Not only is that ugly and introduces temp variables, it's also not getting prettier when I inline the casts.

I would like to know if that is just the ugly truth about Java generics and the limit thereof or if I am doing something wrong.

According to Result's merge declaration you can merge a Result<S> with a Result<T> if and only if T is a subtype of S.

In your aggregate method, left's type parameter is some unknown subclass of Node, and right's type parameter is also some unknown subclass of Node. There's no guarantee that right's type parameter is a subclass of left's.

What if you try this:

public <S extends Node, T extends S> Result<? extends Node> aggregate(Result<S> left, Result<T> right) {
    Result<? extends Node> temp = left.mergeWith(right);
    /** do stuff*/
    return temp;
}

Here, we declare two types S and T and require that S is a subtype of Node and T is a subtype of S.

The problem comes from the ? captures, which are independent of each other. Don't use ? captures in class definitions. Use generic type identifiers:

public class SomeVisitor<T extends Node> implements Visitor<Result<T>> {

    public Result<FileNode> visitFile() { }

    /* ... */

    protected Result<T> aggregate(Result<T> left, Result<T> right) {
        Result<T> tempResult = left.mergeWith(right);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM