Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics cannot convert Type in Type

Tags:

java

generics

I'm having trouble with this:

public class Test {

    static class TestType {}

    static class Pair<A,B>{}

    public static void main( String [] args ) throws Exception {

        Collection<? extends TestType> val = null;

        List<? extends TestType> single = 
            testSingle( val ); // OK

        Pair<String,List<? extends TestType>> pair = 
            testPair( val ); // ERROR

    }

    static <T extends TestType> List<T> testSingle( Collection<T> val ){
        return null;
    }

    static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
        return null;
    }

}

Why is the first one working and the second one isn't?

The Error message ist:

Type mismatch: cannot convert from Test.Pair<String,List<capture#3-of ? extends Test.TestType>> to Test.Pair<String,List<? extends Test.TestType>>

EDIT:

using Braj's Answer I setup this to clearify the Problem when not using generic type T:

public class Test {

    static class TestType {}
    static class Implementation extends TestType {}

    static class Pair<A,B>{}

    public static void main( String [] args ) throws Exception {

        Collection<Implementation> val = null;

        List<Implementation> sinle = testSingle( val ); // OK

        Pair<String,List<Implementation>> pair = testPair( val ); // OK
        Pair<String,List<Implementation>> pair2 = testPair2( val ); // ERROR
        Pair<String,List<Implementation>> pair3 = testPair3( val ); // ERROR
        Pair<String,? extends List<Implementation>> pair4 = testPair4( val ); // ERROR

        run( val );

    }

    private static void run( Collection<? extends TestType> val ){

        List<? extends TestType> single = testSingle( val ); // OK

        Pair<String,List<? extends TestType>> pair = testPair( val ); // ERROR
        Pair<String,List<? extends TestType>> pair2 = testPair2( val ); // ERROR
        Pair<String,List<? extends TestType>> pair3 = testPair3( val ); // OK
        Pair<String,? extends List<? extends TestType>> pair4 = testPair4( val ); // OK
        Pair<String,? extends List<? extends TestType>> pairX = testPair( val ); // OK
        //My-Name-Is
        @SuppressWarnings( "unchecked" )
        Pair<String,List<? extends TestType>> fixed = 
            (Pair<String,List<? extends TestType>>)
            (Pair<String,?>) testPair( val ); // OK but ugly and stupid(?)
    }

    private static <T extends TestType> List<T> testSingle( Collection<T> val ){
        return null;
    }

    private static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
        return null;
    }

    // Braj1
    private static <T extends TestType> Pair<String, List<TestType>> testPair2(Collection<T> val) {
        return null;
    }

    // Braj2
    private static <T extends TestType> Pair<String, List<? extends TestType>> testPair3(Collection<T> val) {
        return null;
    }

    // Seelenvirtuose
    private static <T extends TestType> Pair<String, ? extends List<? extends TestType>> testPair4(Collection<T> val) {
        return null;
    }

    // This one works in the way I wanted.
    private static <T extends TestType> void runOK( Collection<T> val ){

        List<T> single = testSingle( val ); // OK

        Pair<String,List<T>> pair = testPair( val ); // OK
    }
}

EDIT2: I can fix this in run() using:

@SuppressWarnings( "unchecked" )
Pair<String,List<? extends TestType>> fixed = 
    (Pair<String,List<? extends TestType>>)
    (Pair<String,?>) testPair( val );

But this is rather ugly and stupid (?).

EDIT3: I edited the above to include Seelenviruose's answer and it's still getting weirder.

I still don't know why this is needed...

EDIT4: Finally got it working without ugly casts: if I use <T extends TestType> run(...) the compiler doesn't complain. I changed it above.

like image 952
Scheintod Avatar asked Jun 17 '14 11:06

Scheintod


3 Answers

The return type of your second method call is

Pair<String,List<? extends TestType>>

Why is that the return type? Well, the compiler infers that type from the input argument val that is of type Collection<? extends TestType>.

The upper bounded wildcards are the Java mechanism to break generic's inherent invariance. With such a construct the following is possible:

Collection<? extends Number> c1 = new LinkedList<Integer>();
Collection<? extends Number> c2 = new HashSet<Double>();

Note, that I introduced two kinds of polymorphism. One for the collection type, and another for the type variable.

Without the upper bounded wildcard the following construct is be a compiler error:

Collection<Number> c3 = new LinkedList<Integer>(); // compiler error

Why the error? Simple: If you have a Collection<Number> you would expect to being able to add a Double object into it. But this would not be allowed into a LinkedList<Integer>. Hence, error.

The downside of this upper bounded wildcard is now, that you can't add anything (except null) into a Collection<? extends Number>.


Cut: If you understood it until now, the rest should be easy.


You now introduced an additional layer into the generic return type. You do not return a list of something, you return a pair of a string and some list.

So the upper bound now must not be on the type T of your list elements. It must be placed onto the list itself!

With the following method declaration

<T extends TestType> Pair<String, ? extends List<T>> testPair(Collection<T> val) { ... }

the following call will be allowed:

Pair<String, ? extends List<? extends TestType>> pair = testPair(val);

But ... I doubt that anything reasonable is possible with such a pair. If so, you must reconsider your design.

like image 116
Seelenvirtuose Avatar answered Oct 29 '22 16:10

Seelenvirtuose


Why is the first one working and the second one isn't?

Because List<ASpecificSubTypeOfTestType> (the return of the first method) is a subtype of List<? extends TestType>, but Pair<String, List<ASpecificSubTypeOfTestType>> (the return of the second method) is not a subtype of Pair<String, List<? extends TestType>>.

Let us change your example from Pair to List, and TestType to Object for a moment:

public class Test {

    public static void main( String [] args ) throws Exception {

        Collection<?> val = null;

        List<?> single = testSingle( val ); // OK

        List<List<?>> pair = testList( val ); // ERROR

    }

    static <T> List<T> testSingle( Collection<T> val ){
        return null;
    }

    static <T> List<List<T>> testList( Collection<T> val ){
        return null;
    }

}

From a technical point of view, List<SomeSpecificType> is a subtype of List<?>, but List<List<SomeSpecificType>> is not a subtype of List<List<?>>, for the same reason that List<String> is not a subtype of List<Object> -- the type parameters are different concrete types (one is List<SomeSpecificType> and the other is List<?>).

For a more practical reasoning, testList returns a List<List<T>> for some T. We don't know what that T is, but we know that it is some concrete type. And this List<List<T>> that is returned is a list that can only contain List<T>. It cannot contain List<S> if S is not T, because List<S> and List<T> are not subtypes of each other. Even though we don't know what T is, we know that there exists some choice of T, and all the element lists must have that T as the type parameter.

On the other hand, the type you're assigning it to, List<List<?>>, is a list that can contain every type of list at the same time. So you can put in a List<Integer> and a List<String>, etc. at the same time and it would be okay. You can never do that with a List<List<T>>, no matter what you choose T to be.

Therefore, the two types are clearly incompatible.

What can go wrong? With the List<List<?>> reference, you can insert List<Integer> and List<String> into the list. And then with the List<List<T>> reference inside the function, you can extract all the elements as List<T>, which they won't be. So it is unsafe.

You might say, what if I never put things into the List<List<?>> reference? Is that safe? But if that were the case, you should instead use List<? extends List<?>>. The ? extends wildcard makes it a consumer, so you cannot insert anything into it except null; and it also makes the type compatible (List<List<SomeSpecificType>> is a subtype of List<? extends List<?>>).

The moral of the story is, wildcards in deeper parameters do not mean what you think they do.

like image 21
newacct Avatar answered Oct 29 '22 16:10

newacct


Try

private static <T extends TestType> Pair<String, List<TestType>> testPair(Collection<T> val) {...}

Pair<String, List<TestType>> pair =  testPair(val);

OR

private static <T extends TestType> Pair<String, List<? extends TestType>> testPair(Collection<T> val) {...}

Pair<String, List<? extends TestType>> pair =  testPair(val);
like image 31
Braj Avatar answered Oct 29 '22 17:10

Braj