21

I am trying to understand java generics and they seem extremely difficult to understand. For example, this is fine...

public class Main {

    public static void main(String[] args) {
        List<?> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

... as is this...

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<T> list) { }
}

... and this ...

public class Main {

    public static void main(String[] args) {
        List<List<List<?>>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

... but this doesn't compile:

public class Main {

    public static void main(String[] args) {
        List<List<?>> list = null;
        method(list);
    }

    public static <T> void method(List<List<T>> list) { }
}

Can someone explain what is going on in simple language?

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • Just curious, was Boris' answer sufficient, or are you still iffy on this? – Radiodef Apr 09 '15 at 06:32
  • 1
    @Radiodef I'm not iffy on this at all now. It took a while but I finally got it. I've been answering loads of generics questions since I wrote this question! – Paul Boddington Apr 09 '15 at 06:36
  • 1
    Alright, no problem. ; ) I saw the comments and thought about writing something additional. It's not often this comes up. [*"Capture conversion is not applied recursively."*](http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.10) Only the wildcard in the first example can be captured. – Radiodef Apr 09 '15 at 06:42
  • @Radiodef The key for me was when I finally grasped that `List>` is a type (whereas `?` isn't). So `List>` means a `List` whose elements have the type `List>`, whereas `List>` means a `List` of some unknown type. – Paul Boddington Apr 09 '15 at 06:45
  • 1
    Yes, and the nature of the error here is that: if you have a `List>` the `?` can be *treated* like a type (captured) as in the first example. But if you have some nested type like `Map, List>>`, only a wildcard in the "outermost" type (so the `Map` but not the `List`) can be captured. Boris' answer demonstrates *why* (we could do unsafe stuff) but not the particulars of the *how* (capture). A wildcard has slightly different semantics, depending on if it's in an "outer" or "inner" type. – Radiodef Apr 09 '15 at 06:58

1 Answers1

14

The main thing to understand with generic types, is that they aren't covariant.

So whilst you can do this:

final String string = "string";
final Object object = string;

The following will not compile:

final List<String> strings = ...
final List<Object> objects = strings;

This is to avoid the situations where you circumvent the generic types:

final List<String> strings = ...
final List<Object> objects = strings;
objects.add(1);
final String string = strings.get(0); <-- oops

So, going through your examples one by one

1

Your generic method takes a List<T>, you pass in a List<?>; which is (essentially) a List<Object>. T can be assigned to the Object type and the compiler is happy.

2

Your generic method is the same, you pass in a List<List<?>>. T can be assigned to the List<?> type and the compiler is again happy.

3

This is basically the same as 2 with another level of nesting. T is still the List<?> type.

4

Here is where it goes a little pear shaped, and where my point from above comes in.

Your generic method takes a List<List<T>>. You pass in a List<List<?>>. Now, as generic types are not covariant, List<?> cannot be assigned to a List<T>.

The actual compiler error (Java 8) is:

required: java.util.List<java.util.List<T>> found: java.util.List<java.util.List<?>> reason: cannot infer type-variable(s) T (argument mismatch; java.util.List<java.util.List<?>> cannot be converted to java.util.List<java.util.List<T>>)

Basically the compiler is telling you that it cannot find a T to assign because of having to infer the type of the List<T> nested in the outer list.

Lets look at this in a little more detail:

List<?> is a List of some unknown type - it could be a List<Integer> or a List<String>; we can get from it as Object, but we cannot add. Because otherwise we run into the covariance issue I mentioned.

List<List<?>> is a List of List of some unknown type - it could be a List<List<Integer>> or a List<List<String>>. In case 1 it was possible to assign T to Object and just not allow add operations on wildcard list. In case 4 this cannot be done - primarily because there is not a generics construct to prevent add to the outer List.

If the compiler were to assign T to Object in the second case then something like the following would be possible:

final List<List<Integer>> list = ...
final List<List<?>> wildcard = list;
wildcard.add(Arrays.asList("oops"));

So, due to covariance, it is not possible to assign a List<List<Integer>> to any other generic List safely.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • This is almost convincing me. The only quibble is in example 1 when you say T can be assigned to the Object type. That doesn't seem right to me - it doesn't work if you replace the method by a non-generic method taking a List. Surely in example 1 the compiler is able to see that a type T can be found to match a List> to a List. Why can't the compiler see the same thing in example 4? – Paul Boddington Dec 14 '14 at 01:54
  • Same here - it sounds convincing, but ... not completely: You can call the last method when explicitly passing in a `List>`, so the argument should not be referring to missing covariance, but rather to a weakness of the type inference system and the wildcard that is involved there.... – Marco13 Dec 14 '14 at 01:57
  • 2
    Don't you mean `objects.add(1);`? – Ryan Dougherty Dec 14 '14 at 03:47
  • @pbabcdefp added a clarifying edit - hopefully that explains the issue more clearly. – Boris the Spider Dec 14 '14 at 08:54
  • Some things that are prevented are without risk of the exceptions you mention. E.g. if I have a method with signature `static List> m1(List a)` and another one with signature `static void m2(List> a)`, you will find that if `x` is of type `List>` then `m2(m1(x))` doesn't compile! The value returned by a method returning a `List>` isn't accepted as a parameter to a method with an argument of type `List>`. But `m2(m1(x))` is risk-free - you can write a method with parameter `List` whose body is `m2(m1(x));` and it accepts a `List>` just fine. – Paul Boddington Dec 14 '14 at 13:55
  • Actually I'm wrong... `m2(m1(x))` executes fine. But IntelliJ doesn't realise it's going to, and issues the error message method2(java.util.List) cannot be applied to method2(java.util.List>). If the authors of IntelliJ don't understand this stuff, what hope do the rest of us have? – Paul Boddington Dec 14 '14 at 14:49
  • @pbabcdefp I use IntelliJ - it does have some issues with type inference. I recommend that you log it as a bug - help the community out! – Boris the Spider Dec 14 '14 at 14:53