java - Doubly-boxed wildcard return type has an unexpected extra wildcard - Stack Overflow

I have a method that returns a doubly-boxed generic type. When that generic type is a wildcard, the ret

I have a method that returns a doubly-boxed generic type. When that generic type is a wildcard, the return type is not the one that I expect.

An object of type Codec<T> can produce a Box<Box<T>>, so I would expect a Codec<?> to return a Box<Box<?>>, but instead it seems to return a Box<? extends Box<?>>. Where does that extra wildcard around the inner box come from ?

Minimal code for reproducing:

interface Codec<A>
{
    interface Box<B>{}

    public abstract Box<Box<A>> decode();

    static public <T> void SimpleDecode(Codec<T> codec){
        Box<Box<T>> result = codec.decode();
    }

    static public void ExpectedWildCardDecode(Codec<?> codec){
        Box<Box<?>> result = codec.decode();
        // Type mismatch: cannot convert from Codec.Box<Codec.Box<capture#1-of ?>> to Codec.Box<Codec.Box<?>>Java(16777233)
    }

    static public void ObservedWildcardDecode(Codec<?> codec){
        Box<? extends Box<?>> result = codec.decode();
        // Works, but why ?
    }
}

Codec, Box and decode are stand-ins for library classes and functions which I do not control.

Similar behaviour occurs with Codec<? extends T>.

I've already figured some workarounds for my use case, (sometimes using unchecked casts, sometimes using library-specific conversion methods), but I still want to understand why this did not work in the first place.

This was compiled for Java 21 with Gradle 8.11.1.

I have a method that returns a doubly-boxed generic type. When that generic type is a wildcard, the return type is not the one that I expect.

An object of type Codec<T> can produce a Box<Box<T>>, so I would expect a Codec<?> to return a Box<Box<?>>, but instead it seems to return a Box<? extends Box<?>>. Where does that extra wildcard around the inner box come from ?

Minimal code for reproducing:

interface Codec<A>
{
    interface Box<B>{}

    public abstract Box<Box<A>> decode();

    static public <T> void SimpleDecode(Codec<T> codec){
        Box<Box<T>> result = codec.decode();
    }

    static public void ExpectedWildCardDecode(Codec<?> codec){
        Box<Box<?>> result = codec.decode();
        // Type mismatch: cannot convert from Codec.Box<Codec.Box<capture#1-of ?>> to Codec.Box<Codec.Box<?>>Java(16777233)
    }

    static public void ObservedWildcardDecode(Codec<?> codec){
        Box<? extends Box<?>> result = codec.decode();
        // Works, but why ?
    }
}

Codec, Box and decode are stand-ins for library classes and functions which I do not control.

Similar behaviour occurs with Codec<? extends T>.

I've already figured some workarounds for my use case, (sometimes using unchecked casts, sometimes using library-specific conversion methods), but I still want to understand why this did not work in the first place.

This was compiled for Java 21 with Gradle 8.11.1.

Share Improve this question asked Mar 23 at 18:12 EsteckaEstecka 4002 silver badges16 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

The type of the expression codec.decode() is not exactly Box<? extends Box<?>>. It is Box<Box<CAP#1>>, where CAP#1 is a fresh type variable. This just so happens to be convertible to Box<? extends Box<?>>, since Box<CAP#1> is a subtype of Box<?>.

This type is not denotable in Java, but you can say var result = codec.decode(); to have result actually be that type. Or you call a helper method like this:

static public void ObservedWildcardDecode(Codec<?> codec){
    helper(codec.decode());
}

static <T> void helper(Box<Box<T>> result) {
    // do things with 'result' here...
    // 'result' will have the same capabilities as 'Box<Box<CAP#1>>'
}

The type of the expression is not Box<Box<?>> because the return type of the method, Box<Box<?>>, undergoes capture conversion. This is a conversion that replaces all the wildcards in a type with fresh type variables with appropriate bounds (in this case, I have called the fresh type variable CAP#1). This is to capture (no pun intended) the idea that decode can return Box<Box<A>>, Box<Box<B>>, or Box<Box<C>>, or Box<Box<AnythingElse>>.

To see why this is needed, suppose decode returns a List<List<A>> instead (which is isomorphic to Box<Box<A>> from the type system's perspective). What can you do to this returned list? You cannot safely add a List<String> to this list of lists, because decode could have returned a List<Integer> instead.

List<List<?>> result = codec.decode(); // suppose this compiles
result.add(List.of(new Object())); // this could be unsafe if "codec.decode" actually returns a List<List<Integer>>!
// example implementation of Codec
class MyCodec implements Codec<Integer> {
    private List<List<Integer>> list = new ArrayList<>();

    @Override
    public List<List<Integer>> decode() {
        return list;
    }
    
    public void someOtherMethod() {
        // this would go very wrong if list.get(0).get(0) is actually just a 'new Object()'
        System.out.println(list.get(0).get(0) + 1);
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744276748a4566367.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信