3

Every time I think I understand generics better (and can answer without compiling), I get to an example where this theory breaks. Here is a very simple example:

static void consumer(List<? super List<String>> param) {
    System.out.println(param);
}

And two invocations:

public static void main(String[] args) {
    List<String> list = List.of("123");
    consumer(list);
    consumer(List.of("123"));
}

To me, none of the invocations should compile. A String is not a supertype of List. Still, the second one compiles. But let's suppose that this happens because the compiler could infer some type here. Of course such a type does not exist and it will fail at runtime, right? Right? Nope. It just works. As such, can someone bring some sanity to my life please?

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Hmm? You didn't write `List>`, you wrote `List super List>`. – chrylis -cautiouslyoptimistic- Oct 23 '20 at 03:53
  • 1
    The compiler could infer the generic parameter of `List.of` as `Object`... (don't know if that's what actually happens though). – Sweeper Oct 23 '20 at 03:55
  • @Sweeper yes, that is the _only_ explanation I had for myself too... – Eugene Oct 23 '20 at 03:59
  • How did you reach `X extends String implements List`? All the compiler has to do is find a type which satisfies `super List`, and can also be converted from `String` (i.e. a superclass of `String`). And `Object` is found. Why `X extends String implements List`? – Sweeper Oct 23 '20 at 04:01
  • @Sweeper nvm, edited that. But anyway, you actually made me remember of the beautiful `--debug=verboseResolution=all` (and sometimes helpful) option... – Eugene Oct 23 '20 at 04:04

2 Answers2

9

Ah darn!

javac  --debug=verboseResolution=all Sandbox.java

shows that consumer(List.of("123")) is compiled to:

instantiated signature: (Object)List<Object>
target-type: List<? super List<String>>
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Wow I didn't know I could see exactly how javac thinks about my source code! Learned something new today! – Sweeper Oct 23 '20 at 04:08
  • @Sweeper that isn't a documented option though. it might change (go away) in any release. I randomly upvoted one of your q, after all you made me remember this option. – Eugene Oct 23 '20 at 04:09
  • A nice one Eugene, +1 for `--debug=verboseResolution=all`. You might want to add some explanation about the findings. I am not sure I can interpret it correctly. – Nikolas Charalambidis Oct 23 '20 at 08:44
2

If you want a more “functional” explanation, you have to consider what param is.

By applying PECS, notice that it is thus a List that is a consumer of List<String>.

  • Compatible consumers are those which can consume List<String> or any of its parent types;
  • When applied to List, it means a list on which you can call add() with any parent type of List<String>. A List<Object> is thus compatible.

This is why consumer() can be called with a List<Object> but not a List<String> (String is not a supertype of List<String>).

Since List.of(…) can always match List<Object> at declaration time, it accepts the second call.

Note that from inside the consumer() method, you will never be able to retrieve a List<String> from param (i.e. use it as a producer). You can only add new ones into it (i.e. use it as a consumer) – and indeed, you can add a List<String> into a List<Object> (although in this particular case, List.of() produces an immutable list, so it will fail at runtime).

Didier L
  • 18,905
  • 10
  • 61
  • 103
  • thank you. I pretty much know this, my question was what it is inferred to. It turns out it is rather simple in the end. – Eugene Oct 29 '20 at 14:57