Java nested generic type mismatch

2019-01-14 07:56发布

问题:

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>>`)

回答1:

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!


回答2:

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>.



回答3:

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.



回答4:

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 ");
}


回答5:

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>());