泛型的泛型如何工作? [英] How do generics of generics work?

查看:115
本文介绍了泛型的泛型如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

虽然我的确了解了一些泛型的角落案例,但我错过了以下示例。



我有以下班级

  1 public class Test< T> {
2 public static void main(String [] args){
3 Test<扩展Number> t = new Test< BigDecimal>();
4清单<测试<扩展Number>> l = Collections.singletonList(t);
5}
6}

第4行给出错误

 类型不匹配:无法从List< Test< capture#1-of?扩展Number>> 
到List< Test<扩展Number>>`。

显然,编译器认为不同的并不完全相同。尽管我的直觉告诉我,但这是正确的。



任何人都可以提供一个例子,说明如果第4行是合法的,我会得到运行时错误?

<为了避免混淆,我将第3行中的 = null 替换为具体的任务

解决方案

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

 列表<测试<?扩展Number>> l = 
收藏。<测试< extends Number>> singletonList(t);

这立即告诉我们该操作不是不安全的,它只是有限推论的受害者。如果它不安全,上面的代码就不会编译。

由于在上面的泛型方法中使用显式类型参数只能用作提示,所以我们可以推测它是必需的这是推理机的技术限制。事实上,Java 8编译器目前预计会随许多类型推理改进一起发布。我不确定你的具体情况是否会得到解决。



那么,究竟发生了什么?



好,我们得到的编译错误显示 Collections.singletonList 的类型参数 T 被推断为捕获<试验< ;?扩展Number>> 。换句话说,通配符具有一些与其关联的元数据,将其链接到特定的上下文。




  • 想想捕获通配符的最佳方法( capture< ;? extends Foo> )是相同边界的 unnamed 类型参数(即< T extends Foo> ),但不能引用 T )。

  • 释放捕获能力的最佳方法是将其绑定到通用方法的指定类型参数。我将在下面的示例中演示这一点。请参阅Java教程通配符捕获和帮助器方法(感谢您参考@WChargin )进一步阅读。



假设我们希望有一种方法可以将列表转移到后面。然后让我们假设我们的列表有一个未知(通配符)类型。

  public static void main(String ... args){ 
列表< ;? extends String> list = new ArrayList<>(Arrays.asList(a,b,c));
列表< ;? extends String> cycledTwice =循环(循环(列表));
}

public static< T>列表与LT; T>周期(列表< T>列表){
list.add(list.remove(0));
返回列表;

$ / code>

这很好,因为 T 解析为捕获<?扩展字符串> ,而不是?扩展字符串。如果我们改用循环的非通用实现:

  public static List <? extends String>循环(List< ;? extends String> list){
list.add(list.remove(0));
返回列表;
}

它将无法编译,因为我们还没有通过因此,这开始解释了为什么 singletonList 的用户可以从类型中受益-inferer解析 T 测试<捕获<扩展Number> ,并因此返回一个 List< Test< capture<扩展Number>>> 而不是 List< Test< ;?扩展Number>>

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



为什么我们不能只分配一个列表<试验<捕获< ;?将Number>>>< / code>扩展到 List< Test< ;?如果我们考虑到 capture <?>的事实, extends Number> 等价于一个具有 Number 的上限的匿名类型参数,那么我们可以把这个问题变成为什么不下面编译? (它不!):

  public static< T extends Number>列表<试验< ;?扩展Number>> assign(List< Test< t> t){
return t;
}

这是不编译的好理由。如果是这样,那么这是可能的:

  //所有这些都是有效的
列表< Test< Double> ;> doubleTests = null;
列表<测试<扩展Number>> numberTests = assign(doubleTests);

测试<整数> integerTest = null;
numberTests.add(integerTest); //输入错误,现在doubleTests包含一个Test< Integer>



那为什么要做显式的工作?



让我们循环回到开头。如果上述内容不安全,那么这是怎么来的?

  List< Test<扩展Number>> l = 
收藏。<测试< extends Number>> singletonList(t);

为此,它意味着允许以下内容:

 测试<捕捉<扩展Number>> capturedT; 
测试<?扩展Number> t = capturedT;

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

  public static< T extends Number>测试与LT ;?扩展Number>分配(Test< T> t){
return t;
}

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

  List 

的用例。扩展Number> l =新列表< Double>();


While I do understand some of the corner-cases of generics, I'm missing something with the following example.

I have the following 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 }

Line 4 gives me the error

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

Obviously, the compiler thinks that the different ? are not really equal. While my gut-feeling tells me, this is correct.

Can anyone provide an example where I would get a runtime-error if line 4 was legal?

EDIT:

To avoid confusion, I replaced the =null in Line 3 by a concrete assignment

解决方案

As Kenny has noted in his comment, you can get around this with:

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

This immediately tells us that the operation isn't unsafe, it's just a victim of limited inference. If it were unsafe, the above wouldn't compile.

Since using explicit type parameters in a generic method as above is only ever necessary to act as a hint, we can surmise that it being required here is a technical limitation of the inference engine. Indeed, the Java 8 compiler is currently slated to ship with many improvements to type-inference. I'm not sure whether your specific case will be resolved.

So, what's actually happening?

Well, the compile error we're getting shows that the type parameter T of Collections.singletonList is being inferred to be capture<Test<? extends Number>>. In other words, the wildcard has some metadata associated with it that links it to a specific context.

  • The best way to think of a capture of a wildcard (capture<? extends Foo>) is as an unnamed type parameter of the same bounds (i.e. <T extends Foo>, but without being able to reference T).
  • The best way to "unleash" the power of the capture is by binding it to a named type parameter of a generic method. I'll demonstrate this in an example below. See the Java tutorial "Wildcard Capture and Helper Methods" (thanks for the reference @WChargin) for further reading.

Say we want to have a method that shifts a list, wrapping to the back. Then let's assume that our list has an unknown (wildcard) type.

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;
}

This works fine, because T is resolved to capture<? extends String>, not ? extends String. If we instead used this non-generic implementation of cycle:

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

It would fail to compile, because we haven't made the capture accessible by assigning it to a type parameter.

So this begins to explain why the consumer of singletonList would benefit from the type-inferer resolving T to Test<capture<? extends Number>, and thus returning a List<Test<capture<? extends Number>>> instead of a List<Test<? extends Number>>.

But why isn't one assignable to the other?

Why can't we just assign a List<Test<capture<? extends Number>>> to a List<Test<? extends Number>>?

Well if we think about the fact that capture<? extends Number> is the equivalent of an anonymous type parameter with an upper bound of Number, then we can turn this question into "Why doesn't the following compile?" (it doesn't!):

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

This has a good reason for not compiling. If it did, then this would be possible:

//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>

So why does being explicit work?

Let's loop back to the beginning. If the above is unsafe, then how come this is allowed:

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

For this to work, it implies that the following is allowed:

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

Well, this isn't valid syntax, as we can't reference the capture explicitly, so let's evaluate it using the same technique as above! Let's bind the capture to a different variant of "assign":

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

This compiles successfully. And it's not hard to see why it should be safe. It's the very use case of something like

List<? extends Number> l = new List<Double>();

这篇关于泛型的泛型如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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