Skip to content

Instantly share code, notes, and snippets.

@eyeahs
Last active September 13, 2018 01:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eyeahs/47ed3a818ca6b6c7c3a73fdb1bf321b6 to your computer and use it in GitHub Desktop.
Save eyeahs/47ed3a818ca6b6c7c3a73fdb1bf321b6 to your computer and use it in GitHub Desktop.
Generic study

Ref : https://stackoverflow.com/questions/3546745/multiple-wildcards-on-a-generic-methods-makes-java-compiler-and-me-very-confu

As Appendix B indicates, this has nothing to do with multiple wildcards, but rather, misunderstanding what List<List<?>> really means.

Let's first remind ourselves what it means that Java generics is invariant:

An Integer is a Number A List is NOT a List A List IS a List<? extends Number> We now simply apply the same argument to our nested list situation (see appendix for more details):

A List is (captureable by) a List A List> is NOT (captureable by) a List> A List<List> IS (captureable by) a List> With this understanding, all of the snippets in the question can be explained. The confusion arises in (falsely) believing that a type like List<List<?>> can capture types like List<List>, List<List>, etc. This is NOT true.

That is, a List<List<?>>:

is NOT a list whose elements are lists of some one unknown type. ... that would be a List> Instead, it's a list whose elements are lists of ANY type. Snippets

Here's a snippet to illustrate the above points:

List<List> lolAny = new ArrayList>();

lolAny.add(new ArrayList()); lolAny.add(new ArrayList());

// lolAny = new ArrayList<List>(); // DOES NOT COMPILE!!

List> lolSome;

lolSome = new ArrayList<List>(); lolSome = new ArrayList<List>(); More snippets

Here's yet another example with bounded nested wildcard:

List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>();

lolAnyNum.add(new ArrayList()); lolAnyNum.add(new ArrayList()); // lolAnyNum.add(new ArrayList()); // DOES NOT COMPILE!!

// lolAnyNum = new ArrayList<List>(); // DOES NOT COMPILE!!

List<? extends List<? extends Number>> lolSomeNum;

lolSomeNum = new ArrayList<List>(); lolSomeNum = new ArrayList<List>(); // lolSomeNum = new ArrayList<List>(); // DOES NOT COMPILE!! Back to the question

To go back to the snippets in the question, the following behaves as expected (as seen on ideone.com):

public class LOLUnknowns1d { static void nowDefinitelyIllegal(List> lol, List list) { lol.add(list); // DOES NOT COMPILE!!! // The method add(capture#1-of ? extends List) in the // type List<capture#1-of ? extends List> is not // applicable for the arguments (List) } public static void main(String[] args) { List list = null; List<List> lolString = null; List<List> lolInteger = null;

    // these casts are valid
    nowDefinitelyIllegal(lolString, list);
    nowDefinitelyIllegal(lolInteger, list);
}

} lol.add(list); is illegal because we may have a List<List> lol and a List list. In fact, if we comment out the offending statement, the code compiles and that's exactly what we have with the first invocation in main.

All of the probablyIllegal methods in the question, aren't illegal. They are all perfectly legal and typesafe. There is absolutely no bug in the compiler. It is doing exactly what it's supposed to do.

References

Angelika Langer's Java Generics FAQ Which super-subtype relationships exist among instantiations of generic types? Can I create an object whose type is a wildcard parameterized type? JLS 5.1.10 Capture Conversion Related questions

Any simple way to explain why I cannot do List animals = new ArrayList()? Java nested wildcard generic won’t compile Appendix: The rules of capture conversion

(This was brought up in the first revision of the answer; it's a worthy supplement to the type invariant argument.)

5.1.10 Capture Conversion

Let G name a generic type declaration with n formal type parameters A1…An with corresponding bounds U1…Un. There exists a capture conversion from G<T1…Tn> to G<S1…Sn>, where, for 1 <= i <= n:

If Ti is a wildcard type argument of the form ? then … If Ti is a wildcard type argument of the form ? extends Bi, then … If Ti is a wildcard type argument of the form ? super Bi, then … Otherwise, Si = Ti. Capture conversion is not applied recursively. This section can be confusing, especially with regards to the non-recursive application of the capture conversion (hereby CC), but the key is that not all ? can CC; it depends on where it appears. There is no recursive application in rule 4, but when rules 2 or 3 applies, then the respective Bi may itself be the result of a CC.

Let's work through a few simple examples:

List<?> can CC List The ? can CC by rule 1 List<? extends Number> can CC List The ? can CC by rule 2 In applying rule 2, Bi is simply Number List<? extends Number> can NOT CC List The ? can CC by rule 2, but compile time error occurs due to incompatible types Now let's try some nesting:

List<List> can NOT CC List> Rule 4 applies, and CC is not recursive, so the ? can NOT CC List> can CC List<List> The first ? can CC by rule 2 In applying rule 2, Bi is now a List<?>, which can CC List Both ? can CC List<? extends List<? extends Number>> can CC List<List> The first ? can CC by rule 2 In applying rule 2, Bi is now a List<? extends Number>, which can CC List Both ? can CC List<? extends List<? extends Number>> can NOT CC List<List> The first ? can CC by rule 2 In applying rule 2, Bi is now a List<? extends Number>, which can CC, but gives a compile time error when applied to List Both ? can CC To further illustrate why some ? can CC and others can't, consider the following rule: you can NOT directly instantiate a wildcard type. That is, the following gives a compile time error:

// WildSnippet1
new HashMap<?,?>();         // DOES NOT COMPILE!!!
new HashMap<List<?>, ?>();  // DOES NOT COMPILE!!!
new HashMap<?, Set<?>>();   // DOES NOT COMPILE!!!

However, the following compiles just fine:

// WildSnippet2
new HashMap<List<?>,Set<?>>();            // compiles fine!
new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!

The reason WildSnippet2 compiles is because, as explained above, none of the ? can CC. In WildSnippet1, either the K or the V (or both) of the HashMap<K,V> can CC, which makes the direct instantiation through new illegal.

shareimprove this answer edited May 23 at 12:09

Community♦ 11 answered Aug 23 '10 at 12:12

polygenelubricants 246k90485579

So basically, this isn't a bug but a feature to avoid lost of time during compilation ? – Colin Hebert Aug 23 '10 at 12:14 1 @Colin: Finding the answer to "Why is it designed like this?" would be awesome. I'm looking for that right now. – polygenelubricants Aug 23 '10 at 12:18

+1 this explanation confirms my gut feeling, but is more convincing than mine :-) As for the why, I still feel that generic subtyping being invariant is a good enough reason for this. – Péter Török Aug 23 '10 at 12:19

I'm lost, if "Capture conversion is not applied recursively" why doesn't this compile : List<? extends List<? extends Number>> lolWhat = new ArrayList<List>(); The second part should be ignored. Did I miss something ? – Colin Hebert Aug 23 '10 at 13:09

@Peter: I'm actually not quite sure if my answer is correct, now. I'm getting more and more confused =( ; @Colin: yes, valid point. I just discovered Appendix II within the 5 minutes window of that revision, which is why I didn't have time to investigate. I think my answer is partly wrong. I'm keeping this up to inspire others to investigate. – polygenelubricants Aug 23 '10 at 13:12

@polygenelubricants I though that "recursive capture conversion" was only applied to MyClass> but the JLS states that non-wilcarded generics are concerned too. Maybe the recursive part is about something like MyClass> then we can talk about recursivity. – Colin Hebert Aug 23 '10 at 13:38

@Colin, @Peter: Okay I think I finally got this now. Sorry if I caused confusion with the recursive capture conversion thing (which I'm still not sure what it's all about); I was thoroughly confused myself, but Peter was on the right track with generic type invariance being the issue, and me misunderstanding what List<List<?>> really means. – polygenelubricants Aug 23 '10 at 15:42

But still why does it work like this ? – Colin Hebert Aug 23 '10 at 16:41

@Colin: The "no recursive capture" part has nothing to do with this problem. That is another issue (which I also still don't quite understand). This issue, the issues brought up in the question, is quite simple. Java generics is invariant, and nested types can be confusing, but if you put the two together very carefully, everything becomes very clear (at least to me, though I may be missing the point you're trying to bring up; maybe you can elaborate on what you're referring to). – polygenelubricants Aug 23 '10 at 16:46

Here is some code : ideone.com/2Yrem . I'm still confused with line 40, why doesn't it compile ? So I wrote the method3 and for line 15 I have a compilation problem. But when I wrote the method4, it worked, how can line 19 work and not 15 ? – Colin Hebert Aug 23 '10 at 17:53 1 @Colin: Nothing special about line 19, lol and list has exactly the same type. A List<List<? extends Number>> and a List<List<? extends Integer>> are two different types. Into the first you can add a List, into the second you can't. In line 15, you can't do lol = list, but you can lol.addAll(list) (see ideone.com/jOF1v ). Similarly line 40, you can't cast a List<List<? extends Integer>> to a List<List<? extends Number>> since they're two incompatible types. – polygenelubricants Aug 23 '10 at 18:19 5 List<? extends List<? extends Number>> can CC List<List>, and the next bullet point is List<? extends List<? extends Number>> can NOT CC List<List>. So it can or it can't? The fourth bullet point seems redundant to me. – Malcolm Mar 25 '13 at 1:18

@polygenelubricants Definitely an amazing answer! – Sajal Dutta Aug 6 '13 at 19:34

Ref: https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java

extends

The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList(); // Number "extends" Number (in this context) List<? extends Number> foo3 = new ArrayList(); // Integer extends Number List<? extends Number> foo3 = new ArrayList(); // Double extends Number

읽기

foo3에서 Number를 읽을 수 있다. foo3에 대입할 수 있는 모든 list는 Number 또는 Number의 서브클래스를 포합하고 있기 때문이다. Integer를 읽을 수 없다. foo3에 List이 대입되어 있을 수 있기 때문이다. Double을 읽을 수 없다. foo3에 List가 대입됭 있을 수 있기 때문이다.

쓰기

Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments: You can't add an Integer because foo3 could be pointing at a List. You can't add a Double because foo3 could be pointing at a List. You can't add a Number because foo3 could be pointing at a List. You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

super

The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList(); // Integer is a "superclass" of Integer (in this context) List<? super Integer> foo3 = new ArrayList(); // Number is a superclass of Integer List<? super Integer> foo3 = new ArrayList(); // Object is a superclass of Integer Reading - Given the above possible assignments, what type of object are you guaranteed to receive when you read from List foo3: You aren't guaranteed an Integer because foo3 could be pointing at a List or List. You aren't guaranteed an Number because foo3 could be pointing at a List. The only guarantee is that you will get an instance of an Object or subclass of Object (but you don't know what subclass). Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments: You can add an Integer because an Integer is allowed in any of above lists. You can add an instance of a subclass of Integer because an instance of a subclass of Integer is allowed in any of the above lists. You can't add a Double because foo3 could be pointing at a ArrayList. You can't add a Number because foo3 could be pointing at a ArrayList. You can't add a Object because foo3 could be pointing at a ArrayList. PECS

Remember PECS: "Producer Extends, Consumer Super".

"Producer Extends" - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list. "Consumer Super" - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list. If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List. Example

Note this example from the Java Generics FAQ. Note how the source list src (the producing list) uses extends, and the destination list dest (the consuming list) uses super:

public class Collections { public static void copy(List<? super T> dest, List<? extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i,src.get(i)); } } Also see How can I add to List<? extends Number> data structures?

package com.company;
import java.util.ArrayList;
import java.util.List;
public class Main {
static class GenericTest {
public <T> void superWildcard(List<? super T> consumer, T t) {
consumer.add(t);
}
public <T> T extendsWildCard(List<? extends T> consumer) {
return consumer.get(0);
}
}
static class Parent {}
static class Child extends Parent {}
static class Grandchild extends Child {}
public static void main(String[] args) {
// write your code here
List<Object> objectList = new ArrayList<>();
List<Parent> parentList = new ArrayList<>();
List<Child> childList = new ArrayList<>();
List<Grandchild> grandchildList = new ArrayList<>();
GenericTest genericTest = new GenericTest();
// Super 와일드카드 테스트 (T의 타입 파라미터는 Child)
// Child는 Object이기도 하므로 Parent의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(objectList, new Child());
// Child는 Parent이기도 하므로 Parent의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(parentList, new Child());
// Child의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(childList, new Child());
// Grandchild의 List에 Child를 넣는 것은 허용되지 않는다
genericTest.superWildcard(grandchildList, new Child());
// Extends 와일드카드 테스트 (T의 타입 파라미터는 Child)
// Object는 Child를 상속받지 않으므로 허용되지 않는다
Child objectChild = genericTest.extendsWildCard(objectList);
// Parent는 Child를 상속받지 않으므로 허용되지 않는다
Child parentChild = genericTest.extendsWildCard(parentList);
Child childChild = genericTest.extendsWildCard(childList);
Child grandChildChild = genericTest.extendsWildCard(grandchildList);
}
}
public class Main {
static class GenericTest {
public <T> void superWildcard(List<? super T> consumer, T t) {
consumer.add(t);
}
public <T> T extendsWildCard(List<? extends T> consumer) {
return consumer.get(0);
}
}
static class Parent {}
static class Child extends Parent {}
static class Grandchild extends Child {}
public static void main(String[] args) {
// write your code here
List<Object> objectList = new ArrayList<>();
List<Parent> parentList = new ArrayList<>();
List<Child> childList = new ArrayList<>();
List<Grandchild> grandchildList = new ArrayList<>();
GenericTest genericTest = new GenericTest();
// Super 와일드카드 테스트 (T의 타입 파라미터는 Child)
// Child는 Object이기도 하므로 Parent의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(objectList, new Child());
// Child는 Parent이기도 하므로 Parent의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(parentList, new Child());
// Child의 List에 Child를 넣는 것은 허용된다
genericTest.superWildcard(childList, new Child());
// Grandchild의 List에 Child를 넣는 것은 허용되지 않는다
genericTest.superWildcard(grandchildList, new Child());
// Extends 와일드카드 테스트 (T의 타입 파라미터는 Child)
// Object는 Child를 상속받지 않으므로 허용되지 않는다
Child objectChild = genericTest.extendsWildCard(objectList);
// Parent는 Child를 상속받지 않으므로 허용되지 않는다
Child parentChild = genericTest.extendsWildCard(parentList);
Child childChild = genericTest.extendsWildCard(childList);
Child grandChildChild = genericTest.extendsWildCard(grandchildList);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment