gpt4 book ai didi

java - 泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑

转载 作者:IT老高 更新时间:2023-10-28 20:22:55 25 4
gpt4 key购买 nike

让我们首先考虑一个简单的场景( 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!
}
}

这两个通配符是不相关的,这就是为什么您可以调用 doNothingList<String>和一个 List<Integer> .换句话说,两个 ?可以指完全不同的类型。因此,以下内容无法编译,这是意料之中的( 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 ?>)
}
}

到目前为止一切顺利,但事情开始变得非常困惑( 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???
}
}

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

事实上,以下朝该方向的轻微修改不会编译,这是意料之中的( 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 ?>)
}
}

所以看起来编译器正在做它的工作,但是我们得到了这个( 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???
}
}

同样,我们可能有例如 List<List<Integer>> lol和一个 List<Float> list ,所以这不应该编译,对吧?

其实让我们回到更简单的 LOLUnknowns1 (两个无界通配符)并尝试查看我们是否实际上可以调用 probablyIllegal以任何方式。让我们先尝试“简单”的情况,并为两个通配符选择相同的类型( 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>)
}
}

这毫无意义!在这里,我们甚至没有尝试使用两种不同的类型,而且它不会编译!使其成为 List<List<Integer>> lolList<String> list也给出了类似的编译错误!事实上,根据我的实验,代码编译的唯一方法是第一个参数是显式 null类型( 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
}
}

所以问题是,关于 LOLUnknowns1 , LOLUnknowns1aLOLUnknowns1b :
  • 什么类型的参数probablyIllegal接受?
  • lol.add(list);完全编译?它是类型安全的吗?
  • 这是编译器错误还是我误解了通配符的捕获转换规则?


  • 附录A:双重LOL?

    如果有人好奇,这可以很好地编译( 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);
    }
    }

    附录 B:嵌套通配符——它们的真正含义是什么???

    进一步的调查表明,多个通配符可能与问题无关,而嵌套通配符才是混淆的根源。
    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<?>>
    }
    }

    所以它看起来可能是 List<List<String>>不是 List<List<?>> .事实上,虽然任何 List<E>List<?> ,它看起来不像任何 List<List<E>>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<?>>
    }
    }

    那么出现了一个新问题:什么是 List<List<?>> ?

    最佳答案

    如附录 B 所示,这与多个通配符无关,而是误解了 List<List<?>>真正意思。
    让我们首先提醒自己 Java 泛型是不变的意味着什么:

  • IntegerNumber
  • 一个 List<Integer>不是 List<Number>
  • 一个 List<Integer>List<? extends Number>

  • 我们现在简单地将相同的参数应用于我们的嵌套列表情况(有关更多详细信息,请参见附录):
  • 一个 List<String>是(可被捕获)List<?>
  • 一个 List<List<String>>不是(可被捕获)List<List<?>>
  • 一个 List<List<String>> IS(可被捕获)List<? extends List<?>>

  • 有了这种理解,就可以解释问题中的所有片段。混淆产生于(错误地)相信像 List<List<?>> 这样的类型。可以捕获类似 List<List<String>> 的类型, List<List<Integer>>等。这不是真的。
    也就是说,一个 List<List<?>> :
  • 不是一个列表,其元素是某种未知类型的列表。
  • ... 那将是 List<? extends List<?>>

  • 相反,它是一个列表,其元素是任何类型的列表。

  • 片段
    下面是一个片段来说明以上几点:
    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>>();

    更多片段
    这是另一个带有有界嵌套通配符的示例:
    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!!

    回到问题
    回到问题中的片段,以下行为符合预期( 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);是非法的,因为我们可能有 List<List<String>> lol和一个 List<Object> list .事实上,如果我们注释掉有问题的语句,代码就会编译,这正是我们在 main 中的第一次调用时所做的。 .
    所有的 probablyIllegal问题中的方法不违法。它们都是完全合法且类型安全的。编译器绝对没有错误。它正在做它应该做的事情。

    引用
  • 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

  • 相关问题
  • Any simple way to explain why I cannot do List<Animal> animals = new ArrayList<Dog>() ?
  • Java nested wildcard generic won’t compile

  • 附录:捕获转换规则

    (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.


    本节可能会令人困惑,特别是关于捕获转换的非递归应用(特此 CC),但关键是并非所有 ?可以抄送;这取决于它出现的位置。规则 4 中没有递归应用,但是当规则 2 或 3 应用时,相应的 Bi 本身可能是 CC 的结果。
    让我们来看看几个简单的例子:
  • List<?>可以抄送List<String>
  • ?可以按规则 1 抄送

  • List<? extends Number>可以抄送List<Integer>
  • ?可以按规则 2 抄送
  • 在应用规则 2 时,Bi 只是 Number

  • List<? extends Number>不能抄送 List<String>
  • ?可以按规则 2 CC,但由于类型不兼容而导致编译时错误


  • 现在让我们尝试一些嵌套:
  • List<List<?>>不能抄送 List<List<String>>
  • 规则 4 适用,并且 CC 不是递归的,所以 ?不能抄送

  • List<? extends List<?>>可以抄送List<List<String>>
  • 第一 ?可以按规则 2 抄送
  • 在应用规则 2 时,Bi 现在是 List<?> , 可以抄送 List<String>
  • 两者 ?可以抄送

  • List<? extends List<? extends Number>>可以抄送List<List<Integer>>
  • 第一?可以按规则 2 抄送
  • 在应用规则 2 时,Bi 现在是 List<? extends Number> , 可以抄送 List<Integer>
  • 两者 ?可以抄送

  • List<? extends List<? extends Number>>不能抄送 List<List<Integer>>
  • 第一?可以按规则 2 抄送
  • 在应用规则 2 时,Bi 现在是 List<? extends Number> ,它可以 CC,但在应用于 List<Integer> 时会出现编译时错误
  • 两者 ?可以抄送


  • 进一步说明为什么有些 ?可以 CC 而其他人不能,请考虑以下规则:您不能直接实例化通配符类型。也就是说,以下给出了编译时错误:
        // WildSnippet1
    new HashMap<?,?>(); // DOES NOT COMPILE!!!
    new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!!
    new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
    但是,以下编译得很好:
        // WildSnippet2
    new HashMap<List<?>,Set<?>>(); // compiles fine!
    new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
    原因 WildSnippet2编译是因为,如上所述,没有 ?可以抄送。在 WildSnippet1 , 要么 KV (或两者) HashMap<K,V> can CC,通过 new直接实例化非法的。

    关于java - 泛型方法上的多个通配符使 Java 编译器(和我!)非常困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3546745/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com