Multiple wildcards on a generic methods makes Java compiler (and me!) very confused

后端 未结 3 1164
[愿得一人]
[愿得一人] 2020-11-22 16:03

Let\'s first consider a simple scenario (see complete source on ideone.com):

import java.util.*;

public class TwoListsOfUnknowns {
    static void doNothing         


        
相关标签:
3条回答
  • 2020-11-22 16:54

    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:

    1. An Integer is a Number
    2. A List<Integer> is NOT a List<Number>
    3. A List<Integer> IS a List<? extends Number>

    We now simply apply the same argument to our nested list situation (see appendix for more details):

    1. A List<String> is (captureable by) a List<?>
    2. A List<List<String>> is NOT (captureable by) a List<List<?>>
    3. A List<List<String>> IS (captureable by) a List<? extends 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<String>>, List<List<Integer>>, 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<? extends 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<List<?>>();
    
    lolAny.add(new ArrayList<Integer>());
    lolAny.add(new ArrayList<String>());
    
    // lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!!
    
    List<? extends List<?>> lolSome;
    
    lolSome = new ArrayList<List<String>>();
    lolSome = new ArrayList<List<Integer>>();
    

    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<Integer>());
    lolAnyNum.add(new ArrayList<Float>());
    // lolAnyNum.add(new ArrayList<String>());     // DOES NOT COMPILE!!
    
    // lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!!
    
    List<? extends List<? extends Number>> lolSomeNum;
        
    lolSomeNum = new ArrayList<List<Integer>>();
    lolSomeNum = new ArrayList<List<Float>>();
    // lolSomeNum = new ArrayList<List<String>>(); // 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<? extends 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<capture#3-of ?>)
        }
        public static void main(String[] args) {
            List<Object> list = null;
            List<List<String>> lolString = null;
            List<List<Integer>> lolInteger = null;
    
            // these casts are valid
            nowDefinitelyIllegal(lolString, list);
            nowDefinitelyIllegal(lolInteger, list);
        }
    }
    

    lol.add(list); is illegal because we may have a List<List<String>> lol and a List<Object> 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<Animal> animals = new ArrayList<Dog>()?
    • 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:

    1. If Ti is a wildcard type argument of the form ? then …
    2. If Ti is a wildcard type argument of the form ? extends Bi, then …
    3. If Ti is a wildcard type argument of the form ? super Bi, then …
    4. 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<String>
      • The ? can CC by rule 1
    • List<? extends Number> can CC List<Integer>
      • The ? can CC by rule 2
      • In applying rule 2, Bi is simply Number
    • List<? extends Number> can NOT CC List<String>
      • 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<List<String>>
      • Rule 4 applies, and CC is not recursive, so the ? can NOT CC
    • List<? extends List<?>> can CC List<List<String>>
      • The first ? can CC by rule 2
      • In applying rule 2, Bi is now a List<?>, which can CC List<String>
      • Both ? can CC
    • List<? extends List<? extends Number>> can CC List<List<Integer>>
      • The first ? can CC by rule 2
      • In applying rule 2, Bi is now a List<? extends Number>, which can CC List<Integer>
      • Both ? can CC
    • List<? extends List<? extends Number>> can NOT CC List<List<Integer>>
      • 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<Integer>
      • 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.

    0 讨论(0)
  • 2020-11-22 16:56

    not an expert, but I think I can understand it.

    let's change your example to something equivalent, but with more distinguishing types:

    static void probablyIllegal(List<Class<?>> x, Class<?> y) {
        x.add(y); // this compiles!! how come???
    }
    

    let's change List to [] to be more illuminating:

    static void probablyIllegal(Class<?>[] x, Class<?> y) {
        x.add(y); // this compiles!! how come???
    }
    

    now, x is not an array of some type of class. it is an array of any type of class. it can contain a Class<String> and a Class<Int>. this cannot be expressed with ordinary type parameter:

    static<T> void probablyIllegal(Class<T>[] x  //homogeneous! not the same!
    

    Class<?> is a super type of Class<T> for any T. If we think a type is a set of objects, set Class<?> is the union of all sets of Class<T> for all T. (does it include itselft? I dont know...)

    0 讨论(0)
  • 2020-11-22 17:02
    • No argument with generics should be accepted. In the case of LOLUnknowns1b the null is accepted as if the first argument was typed as List. For example this does compile :

      List lol = null;
      List<String> list = null;
      probablyIllegal(lol, list);
      
    • IMHO lol.add(list); shouldn't even compile but as lol.add() needs an argument of type List<?> and as list fits in List<?> it works.
      A strange example which make me think of this theory is :

      static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
          lol.add(list); // compiles fine!!! how come???
      }
      

      lol.add() needs an argument of type List<? extends Number> and list is typed as List<? extends Integer>, it fits in. It won't work if it doesn't match. Same thing for the double LOL, and other nested wildcards, as long as the first capture matches the second one, everything is okay (and souldn't be).

    • Again, I'm not sure but it does really seem like a bug.

    • I'm glad to not be the only one to use lol variables all the time.

    Resources :
    http://www.angelikalanger.com, a FAQ about generics

    EDITs :

    1. Added comment about the Double Lol
    2. And nested wildcards.
    0 讨论(0)
提交回复
热议问题