gpt4 book ai didi

java - 泛型的泛型如何工作?

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

虽然我确实了解泛型的一些极端情况,但我在以下示例中遗漏了一些东西。

我有以下类(class)

1 public class Test<T> {
2 public static void main(String[] args) {
3 Test<? extends Number> t = new Test<BigDecimal>();
4 List<Test<? extends Number>> l =Collections.singletonList(t);
5 }
6 }

第 4 行给了我错误

Type mismatch: cannot convert from List<Test<capture#1-of ? extends Number>> 
to List<Test<? extends Number>>`.

显然,编译器认为不同的 ? 并不真正相等。虽然我的直觉告诉我,这是正确的。

谁能提供一个例子,如果第 4 行是合法的,我会得到一个运行时错误?

编辑:

为避免混淆,我将第 3 行中的 =null 替换为具体赋值

最佳答案

正如肯尼在他的评论中指出的那样,您可以通过以下方式解决这个问题:

List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t);

这立即告诉我们该操作并非不安全,它只是有限推理的受害者。如果它不安全,则上述内容将无法编译。

由于在上述泛型方法中使用显式类型参数只需要充当提示,我们可以推测这里需要它是推理引擎的技术限制。事实上,Java 8 编译器目前预定与 many improvements to type-inference 一起发布。我不确定您的具体情况是否会得到解决。

那么,到底发生了什么?

好吧,我们得到的编译错误表明 T 的类型参数 Collections.singletonList 被推断为 capture<Test<? extends Number>> 。换句话说,通配符有一些与之关联的元数据,将其链接到特定的上下文。

  • 将通配符 (capture<? extends Foo>) 捕获的最佳方法是将其视为具有相同边界的 未命名 类型参数(即 <T extends Foo> ,但无法引用 T )。
  • “释放”捕获功能的最佳方法是将其绑定(bind)到泛型方法的命名类型参数。我将在下面的示例中对此进行演示。请参阅 Java 教程 "Wildcard Capture and Helper Methods"(感谢 @WChargin 的引用)以进一步阅读。

假设我们想要一个方法来移动一个列表,并在后面换行。那么让我们假设我们的列表有一个未知(通配符)类型。

public static void main(String... args) {
List<? extends String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<? extends String> cycledTwice = cycle(cycle(list));
}

public static <T> List<T> cycle(List<T> list) {
list.add(list.remove(0));
return list;
}

这很好用,因为 T 被解析为 capture<? extends String> ,而不是 ? extends String 。如果我们改为使用这种非泛型的循环实现:

public static List<? extends String> cycle(List<? extends String> list) {
list.add(list.remove(0));
return list;
}

它将无法编译,因为我们没有通过将捕获分配给类型参数来使其可访问。

所以这开始解释为什么 singletonList 的消费者会受益于类型推断器将 T 解析为 Test<capture<? extends Number> ,从而返回 List<Test<capture<? extends Number>>> 而不是 List<Test<? extends Number>>

但为什么不能将一个分配给另一个?

为什么我们不能将 List<Test<capture<? extends Number>>> 分配给 List<Test<? extends Number>>

好吧,如果我们考虑 capture<? extends Number> 相当于一个匿名类型参数的上限为 Number 的事实,那么我们可以将这个问题变成“为什么以下内容不能编译?” (它没有!):

public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {
return t;
}

这有一个很好的理由不编译。如果是这样,那么这将是可能的:

//all this would be valid
List<Test<Double>> doubleTests = null;
List<Test<? extends Number>> numberTests = assign(doubleTests);

Test<Integer> integerTest = null;
numberTests.add(integerTest); //type error, now doubleTests contains a Test<Integer>

那么为什么显式有效呢?

让我们回到开头。如果上面不安全,那怎么会允许:

List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t);

为此,它意味着允许以下操作:

Test<capture<? extends Number>> capturedT;
Test<? extends Number> t = capturedT;

好吧,这不是有效的语法,因为我们不能明确地引用捕获,所以让我们使用与上面相同的技术来评估它!让我们将捕获绑定(bind)到“assign”的不同变体:

public static <T extends Number> Test<? extends Number> assign(Test<T> t) {
return t;
}

这编译成功。不难看出为什么它应该是安全的。这就是

之类的用例
List<? extends Number> l = new List<Double>();

关于java - 泛型的泛型如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16449799/

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