Java nested generic type mismatch
Asked Answered
L

5

17

In the following example:

public static void main(String[] args) {

    List<String> b = new ArrayList<String>();
    first(b);
    second(b);

    List<List<String>> a = new ArrayList<List<String>>();
    third(a);
    fourth(a);  // doesnt work

}

private static <T> void first(List<T> a){
    System.out.println("List of T");
}

private static void second(List<?> a){
    System.out.println("List of anything ");
}

private static <T> void third(List<List<T>> a){
    System.out.println("List of a List of T ");
}

private static void fourth(List<List<?>> a){
    System.out.println("List of a List of anything ");
}

Why does the call to second(b) work, but the call to fourth(a) doesn't ?

I get the following error:

The method fourth(List<List<?>>) in the type `TestTest` is not applicable for the arguments (`List<List<String>>`)
Lotic answered 5/11, 2012 at 10:15 Comment(3)
You're facing Type Erasure mechanism docs.oracle.com/javase/tutorial/java/generics/erasure.htmlMarguritemargy
Sure, but why doesn't it work for List<List<String>> ?Lotic
Its not nested - its transitory. big difference.Newkirk
N
17

If you want to be able to call fourth with a List<List<String>> argument, then you'll need to change your signature to this:

private static void fourth(List<? extends List<?>> a){
    System.out.println("List of a List of anything ");
}

The above will work because unlike List<List<?>>, List<? extends List<?>> is compatible with List<List<String>>. Think of it this way:

List<List<String>> original = null;
List<? extends List<?>> ok  = original; // This works
List<?> ok2                 = original; // So does this
List<List<?>> notOk         = original; // This doesn't

List<Integer> original      = null;
List<? extends Number> ok   = original; // This works
List<?> ok2                 = original; // So does this
List<Number> notOk          = original; // This doesn't

The reasoning is simple. If you had

private static void fourth(List<List<?>> a) {
    List<?> ohOh = Arrays.asList(new Object());
    a.add(ohOh);
}

And then if you could call that method as such:

List<List<String>> a = new ArrayList<List<String>>();
fourth(a);
String fail = a.get(0).get(0); // ClassCastException here!
Nardone answered 5/11, 2012 at 10:30 Comment(6)
+1 liked the answer but more explanation is required why ? extends List<?> works?Munguia
Spot-on answer Lukas! But I believe the last line should read String fail = a.get(0).get(0).Swetlana
Why is List<? extends List<?>> a super-type of List<List<String>>?Lotic
@user1799796: I'm not sure if I got the terms right. The types are certainly compatible, though. I have added an analogy, which isn't quite the same thing, but similar. It may feel more intuitive, when you have a List<? extends Number>, rather than a List<? extends List<?>>.Nardone
So basically if you have class A<T>, and a variable A<?> anObj = ... (or A<? extends whatever>), you can't invoke any methods on anObj that have arguments of type T in the declaration, right?Ioved
@Andy: Almost. You can always pass the null literal, as null is a valid value for ?. Try List<?> list = new ArrayList<Object>(); list.add(null);Nardone
M
5

A List<List<String>> isn't a List<List<?>>.

You should be able to put any List<?> into a List<List<?>>, no matter what the ?. A List<List<String>> will only accept a List<String>.

Marceau answered 5/11, 2012 at 10:26 Comment(0)
Z
2

This implies that the type is unknown and objects of any type can be added to List<List<?>> that are heterogeneous and compiler cannot guarantee that all object in List<List<?>> are of same type. Hence it cannot be passed to new ArrayList<List<String>>() that takes a bounded type as parameter.

Zoril answered 5/11, 2012 at 10:35 Comment(0)
M
1

List<List<String>> is not same as List<List<?>>. Generics are invariant in nature. If you only do List<?> and pass List<String> then it will work because List of Anything can be represented by Lists of String.

But List of List of anything can not be represented by List of List of String.

@Lukas Elder has already specified case that will work. Here is the second case that will work

private static void fourth(List<?> a){
    System.out.println("List of anything ");
}
Munguia answered 5/11, 2012 at 10:27 Comment(2)
So your saying there is no flexibility in Java generic type parameters? When a type parameter is used within a generic the instantiated object expects exactly the type specified when the generic type parameter is used?Ridley
@kmb385 I must admit I have failed to interpret meaning of the comment.Flexibility in Generic types using wild cards , bounded wind cards or bounded types.Munguia
J
0
List<List<?>> == List {                 //That contains any unknown type lists
                        List<Integer>,
                        List<String>,
                        List<Object>
                      }

Where as

List<? extends List<?> == List {       //That contains same unknown type lists
                        List<Integer>,
                        List<Integer>,
                        List<Integer>
                      }

So here

 List<List<String>> == List {        //That contains same String lists
                        List<String>,
                        List<String>,
                        List<String>
                      }

Hence List<? extends List<?> is super type of List<List<String>> and assignable.

So valid value to call your fourth method is below.

    List<List<?>> a1 =  new ArrayList<List<?>>();
    a1.add(new ArrayList<String>());
    a1.add(new ArrayList<Integer>());
    a1.add(new ArrayList<Object>());
Janeyjangle answered 8/6, 2016 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.