通用方法上的多个通配符使得Java编译器(和我!)很困惑 [英] Multiple wildcards on a generic methods makes Java compiler (and me!) very confused

查看:564
本文介绍了通用方法上的多个通配符使得Java编译器(和我!)很困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们先来看一个简单的场景(在ideone.com上查看完整的源代码):

  import java.util。*; 

public class TwoListsOfUnknowns {
static void doNothing(List<?> list1,List<?> list2){}

public static void main [] args){
List< String> list1 = null;
List< Integer> list2 = null;
doNothing(list1,list2); //编译好!
}
}

这两个通配符是不相关的,这就是为什么使用列表< String> 列表 调用 doNothing $ c>。换句话说,两个可以指完全不同的类型。因此,以下不会编译,这是预期的(也在ideone.com ):

  import java.util。*; 

public class TwoListsOfUnknowns2 {
static void doSomethingIllegal(List<?> list1,List<?> list2){
list1.addAll(list2); //不能编译!
//在类型List< capture#1-of?>中的方法addAll(Collection< ;? extends capture#1-of?>)
//不适用于
//参数(List< capture#2-of?>)
}
}

到目前为止很好,但这里的地方开始变得非常混乱(如在ideone.com 上所见):

  import java.util。*; 

public class LOLUnknowns1 {
static void maybeIllegal(List< List<?>> lol,List<?> list){
lol.add(list); // this compiles!怎么来的???
}
}

上面的代码在Eclipse中编译, code> sun-jdk-1.6.0.17 在ideone.com,但应该吗?是不是有可能我们有一个 List< List< Integer>> lol List< String>列表,类似两个不相关的通配符情况从 TwoListsOfUnknowns



对该方向的轻微修改不会编译,这是预期的(在ideone.com 上显示):

  import java.util。*; 

public class LOLUnknowns2 {
static void rightfullyIllegal(
List< List< ;? extends Number>> lol,List<?> list){

lol.add(list); //不能编译!正如预期!
//在类型
中的方法add(List< ;? extends Number>)// List< List< ;? extends Number>>不适用于
//参数(List< capture#1-of?>)
}
}

所以看起来编译器正在做它的工作,但是我们得到这个(如在ideone.com上所见):

  import java.util。*; 

public class LOLUnknowns3 {
static void mightIllegalAgain(
List< List< ;? extends Number>> lol,List< ;? extends Number> list){

lol.add(list); //编译好!怎么来的???
}
}

a List< List< Integer>> lol List< Float>列表,所以这不应该编译,对吗?



事实上,让我们回到更简单的 LOLUnknowns1 (两个无限通配符),并尝试看看我们是否可以以任何方式调用 likelyIllegal 。让我们先尝试简单案例,然后为两个通配符选择相同的类型(在ideone.com上显示) ):

  import java.util。*; 

public class LOLUnknowns1a {
static void maybeIllegal(List< List<?>> lol,List<?> list){
lol.add(list); // this compiles!怎么来的???
}

public static void main(String [] args){
List< List< String> lol = null;
List< String> list = null;
maybeIllegal(lol,list); //不能编译!
//在类型LOLUnknowns1a中的方法maybeIllegal(List< List<?>>,List<?>)
//不适用于
// arguments ; List< String>>,List< String>)
}
}

这没有任何意义!这里我们甚至不试图使用两种不同的类型,它不编译!使它成为列表< List< Integer>> lol List< String> list 也会出现类似的编译错误!事实上,从我的实验中,代码编译的唯一方法是如果第一个参数是一个显式的 null 类型(如在ideone.com上看到的):

  import java.util 。*; 

public class LOLUnknowns1b {
static void maybeIllegal(List< List<?>> lol,List<?> list){
lol.add(list); // this compiles!怎么来的???
}

public static void main(String [] args){
List< String> list = null;
maybeIllegal(null,list); //编译好!
//在运行时抛出NullPointerException
}
}

因此,问题是关于 LOLUnknowns1 LOLUnknowns1a LOLUnknowns1b




  • mightIllegal 接受什么类型的参数? >
  • 应该 lol.add(list); 编译吗?是类型安全吗?

  • 这是一个编译器错误还是我误解了通配符的捕获转换规则?


$ b b


附录A:双人LOL?



如果有人好奇, http://ideone.com/U65yx =nofollow noreferrer>如在ideone.com 上显示):

  import java.util。*; 

public class DoubleLOL {
static void omg2xLOL(List< List<?>> lol1,List< List<?>> lol2){
// compile正好!!!
lol1.addAll(lol2);
lol2.addAll(lol1);
}
}






附录B:嵌套通配符 - 它们真正意味着什么?



进一步的调查表明,也许多个通配符与这个问题无关, >嵌套的通配符是混淆的根源。

  import java.util。 

public class IntoTheWild {

public static void main(String [] args){
List<?& list = new ArrayList< String>(); //编译好!

List< List<?>> lol = new ArrayList< List< String>>(); //不能编译!
//类型不匹配:不能从
转换// ArrayList< List< String>>到List< List<?>>
}
}

List< List< String>> 不是 List< List<?> 。事实上,虽然 List< E> List<?> List< List< E> List< List<?> 如在ideone.com上显示):

  import java.util。*; 

public class IntoTheWild2 {
static< E>列表<?> makeItWild(List< E> list){
return list; //编译好!
}
static< E>列表< List<?>> makeItWildLOL(List< List< E>> lol){
return lol; //不能编译!
//类型不匹配:不能从
转换// List< List< E>>到List< List<?>>
}
}

然后出现一个新问题: 列表< List<?>< / code>?

解决方案

B表示,这与多个通配符无关,而是误解了 List< List<?> 真正意味着什么。



让我们先提醒自己,这意味着Java泛型是不变的:


  1. 一个 Integer 是

  2. A 列表< Integer> 不是 a 列表< Number>

  3. A 列表< Integer> ; IS a List< ;? extends Number>

现在我们只需将相同的参数应用于嵌套列表情境




  1. c

  2. A 列表< List< String>> (可由)列表< List<?>> b
  3. A 列表< List< String>> IS (可被捕获)列表< extends List<?>>

根据这个理解,问题中的所有片段解释。混淆出现在(假的)相信诸如 List< List<?>> 之类的类型可以捕获 List< List< String> < / c> c>



也就是说,列表< List<?>>




  • 一个列表,其元素是一些未知类型的列表。


    • ...这将是一个列表<? extends List<?>>


  • > ANY 类型。






片段



这里是一个代码片段来说明上述几点:

 列表< List<?> lolAny = new ArrayList< List<?>>(); 

lolAny.add(new ArrayList< Integer>());
lolAny.add(new ArrayList< String>());

// lolAny = new ArrayList< List< String>>(); //不能编译!

List< ;? extends List<?>> lolSome;

lolSome = new ArrayList< List< String>>();
lolSome = new ArrayList< List< Integer>>();






更多片段



这是另一个有界嵌套通配符的例子:

  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>()); //不能编译!

// lolAnyNum = new ArrayList< List< Integer>>(); //不能编译!

List< ;? extends List< ;? extends number>> lolSomeNum;

lolSomeNum = new ArrayList< List< Integer>>();
lolSomeNum = new ArrayList< List< Float>>();
// lolSomeNum = new ArrayList< List< String>>(); //不能编译!






返回问题



要返回问题中的代码段,以下行为与预期一致(如下所示) ideone.com ):

  public class LOLUnknowns1d {
static void nowDefinitelyIllegal(List< ;? extends List< ; list>>> lol,List<> list){
lol.add(list); //不能编译!
//在
//类型List< capture#1-of中的方法add(capture#1-of?extends List<?> extends List<?>>不是
//适用于参数(List< capture#3-of?>)
}
public static void main(String [] args){
List<对象> list = null;
List< List< String>> lolString = null;
List< List< Integer>> lolInteger = null;

//这些转换是有效的
nowDefinitelyIllegal(lolString,list);
nowDefinitelyIllegal(lolInteger,list);
}
}

lol.add ); 是非法的,因为我们可能有 List< List< String> lol List< Object> list 。事实上,如果我们注释掉违规的语句,代码编译,这正是我们在 main 中的第一次调用。



问题中的所有 mightlllegal 方法都不是非法的。他们都是完全法律和类型安全。编译器中绝对没有错误。






参考





相关问题








附录:捕获转换规则






5.1.10捕获转换



>与对应的边界 。存在从G 1 ... T n > G 1 的捕获转换> ... S n > ,其中,对于 1 <= i <= n


  1. 如果 T i 形式的通配符类型参数, ...

  2. 如果 T i 形式的通配符类型参数?

  3. > 是形式的通配符类型参数?超级 B i ,然后...
  4. 否则 sub> = T i


这部分可能会引起混淆,特别是对于非递归应用捕获转换(因此 CC ),但关键是不是所有都可以CC;它取决于它出现的位置。在规则4中没有递归应用,但是当规则2或3适用时,相应的 B i 本身可以是CC的结果。



让我们通过几个简单的例子:




  • 列表<?> ; 可以CC 列表< String>


    • <$ c $

    • extends Number>
      可以CC 列表<整数>


      • 可以按规则2执行CC

      • 在应用规则2时, B i 只需


    • extends Number> 可以 NOT CC 列表< String>


      • 可以按规则2执行CC,但由于不兼容的类型,会导致编译时错误




    现在让我们尝试一些嵌套:




    • List< List<?>> 可以 NOT CC List< List< String>>


      • 规则4适用,CC不是递归的,因此可以 CC


    • extends List<?>> 可以CC List< List< String>>


      • 第一个可以按规则2执行CC

      • 在应用规则2时, i 现在是列表<?> ,可以是CC List< String>

      • 可以CC


    • 列表< ;? extends List< ;? extends Number>> 可以CC 列表< List< Integer>>


      • 第一个可以按规则2执行CC

      • 在应用规则2时, sub> 现在是列表<?扩展了Number> ,可以CC List< Integer>

      • 可以CC


    • extends List< ;? extends Number>> 可以 CC 列表< List< Integer>>
      $ b b

      • 第一个可以按规则2执行CC

      • 在应用规则2时, B i 现在是列表<?当应用于 List< Integer>

      • 时,会出现编译时错误的Number>
        li> 可以CC


    $ b b

    为了进一步说明为什么某些可以CC和其他人不能,请考虑以下规则:您可以直接实例化通配符类型。也就是说,下面给出了编译时错误:

      // WildSnippet1 
    new HashMap<?,?& (); //不能编译!
    new HashMap< List<?>,?>(); //不能编译!
    new HashMap<?,Set<?>>(); //不能编译!但是,下面的编译很好:


      // WildSnippet2 
    new HashMap< List<?>,Set<?>> //编译好!
    new HashMap< Map<?,?>,Map<?,Map<?,?>>> //编译好!

    原因 WildSnippet2 编译是因为如上所述,都不能是CC。在 WildSnippet1 中, K V 两个) HashMap< K,V> 可以CC,这使得通过 new 的直接实例化是非法的。 p>

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

    import java.util.*;
    
    public class TwoListsOfUnknowns {
        static void doNothing(List<?> list1, List<?> list2) { }
    
        public static void main(String[] args) {
            List<String> list1 = null;
            List<Integer> list2 = null;
            doNothing(list1, list2); // compiles fine!
        }
    }
    

    The two wildcards are unrelated, which is why you can call doNothing with a List<String> and a List<Integer>. In other words, the two ? can refer to entirely different types. Hence the following does not compile, which is to be expected (also on ideone.com):

    import java.util.*;
    
    public class TwoListsOfUnknowns2 {
        static void doSomethingIllegal(List<?> list1, List<?> list2) {
            list1.addAll(list2); // DOES NOT COMPILE!!!
                // The method addAll(Collection<? extends capture#1-of ?>)
                // in the type List<capture#1-of ?> is not applicable for
                // the arguments (List<capture#2-of ?>)
        }
    }
    

    So far so good, but here's where things start to get very confusing (as seen on ideone.com):

    import java.util.*;
    
    public class LOLUnknowns1 {
        static void probablyIllegal(List<List<?>> lol, List<?> list) {
            lol.add(list); // this compiles!! how come???
        }
    }
    

    The above code compiles for me in Eclipse and on sun-jdk-1.6.0.17 in ideone.com, but should it? Is it not possible that we have a List<List<Integer>> lol and a List<String> list, the analogous two unrelated wildcards situations from TwoListsOfUnknowns?

    In fact the following slight modification towards that direction does not compile, which is to be expected (as seen on ideone.com):

    import java.util.*;
    
    public class LOLUnknowns2 {
        static void rightfullyIllegal(
                List<List<? extends Number>> lol, List<?> list) {
    
            lol.add(list); // DOES NOT COMPILE! As expected!!!
                // The method add(List<? extends Number>) in the type
                // List<List<? extends Number>> is not applicable for
                // the arguments (List<capture#1-of ?>)
        }
    }
    

    So it looks like the compiler is doing its job, but then we get this (as seen on ideone.com):

    import java.util.*;
    
    public class LOLUnknowns3 {
        static void probablyIllegalAgain(
                List<List<? extends Number>> lol, List<? extends Number> list) {
    
            lol.add(list); // compiles fine!!! how come???
        }
    }
    

    Again, we may have e.g. a List<List<Integer>> lol and a List<Float> list, so this shouldn't compile, right?

    In fact, let's go back to the simpler LOLUnknowns1 (two unbounded wildcards) and try to see if we can in fact invoke probablyIllegal in any way. Let's try the "easy" case first and choose the same type for the two wildcards (as seen on ideone.com):

    import java.util.*;
    
    public class LOLUnknowns1a {
        static void probablyIllegal(List<List<?>> lol, List<?> list) {
            lol.add(list); // this compiles!! how come???
        }
    
        public static void main(String[] args) {
            List<List<String>> lol = null;
            List<String> list = null;
            probablyIllegal(lol, list); // DOES NOT COMPILE!!
                // The method probablyIllegal(List<List<?>>, List<?>)
                // in the type LOLUnknowns1a is not applicable for the
                // arguments (List<List<String>>, List<String>)
        }
    }
    

    This makes no sense! Here we aren't even trying to use two different types, and it doesn't compile! Making it a List<List<Integer>> lol and List<String> list also gives a similar compilation error! In fact, from my experimentation, the only way that the code compiles is if the first argument is an explicit null type (as seen on ideone.com):

    import java.util.*;
    
    public class LOLUnknowns1b {
        static void probablyIllegal(List<List<?>> lol, List<?> list) {
            lol.add(list); // this compiles!! how come???
        }
    
        public static void main(String[] args) {
            List<String> list = null;
            probablyIllegal(null, list); // compiles fine!
                // throws NullPointerException at run-time
        }
    }
    

    So the questions are, with regards to LOLUnknowns1, LOLUnknowns1a and LOLUnknowns1b:

    • What types of arguments does probablyIllegal accept?
    • Should lol.add(list); compile at all? Is it typesafe?
    • Is this a compiler bug or am I misunderstanding the capture conversion rules for wildcards?

    Appendix A: Double LOL?

    In case anyone is curious, this compiles fine (as seen on ideone.com):

    import java.util.*;
    
    public class DoubleLOL {
        static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
            // compiles just fine!!!
            lol1.addAll(lol2);
            lol2.addAll(lol1);
        }
    }
    


    Appendix B: Nested wildcards -- what do they really mean???

    Further investigation indicates that perhaps multiple wildcards has nothing to do with the problem, but rather a nested wildcard is the source of the confusion.

    import java.util.*;
    
    public class IntoTheWild {
    
        public static void main(String[] args) {
            List<?> list = new ArrayList<String>(); // compiles fine!
    
            List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
                // Type mismatch: cannot convert from
                // ArrayList<List<String>> to List<List<?>>
        }
    }
    

    So it looks perhaps a List<List<String>> is not a List<List<?>>. In fact, while any List<E> is a List<?>, it doesn't look like any List<List<E>> is a List<List<?>> (as seen on ideone.com):

    import java.util.*;
    
    public class IntoTheWild2 {
        static <E> List<?> makeItWild(List<E> list) {
            return list; // compiles fine!
        }
        static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
            return lol;  // DOES NOT COMPILE!!!
                // Type mismatch: cannot convert from
                // List<List<E>> to List<List<?>>
        }
    }
    

    A new question arises, then: just what is a List<List<?>>?

    解决方案

    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

    Related questions


    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.

    这篇关于通用方法上的多个通配符使得Java编译器(和我!)很困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆