仿制药的仿制药如何运作?

Jon*_*han 22 java generics wildcard capture

虽然我确实理解了泛型的一些角落情况,但我遗漏了以下示例的内容.

我有以下课程

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 }
Run Code Online (Sandbox Code Playgroud)

第4行给出了错误

Type mismatch: cannot convert from List<Test<capture#1-of ? extends Number>> 
to List<Test<? extends Number>>`. 
Run Code Online (Sandbox Code Playgroud)

显然,编译器认为不同的不同?.虽然我的直觉告诉我,这是正确的.

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

编辑:

为了避免混淆,我用第=null3行替换了具体的赋值

Mar*_*ers 22

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

List<Test<? extends Number>> l =
    Collections.<Test<? extends Number>>singletonList(t);
Run Code Online (Sandbox Code Playgroud)

这立刻告诉我们操作并不安全,它只是有限推理的受害者.如果它不安全,上面就不会编译.

由于在上面的泛型方法中使用显式类型参数仅作为提示起作用,我们可以推测这里需要的是推理引擎的技术限制.实际上,Java 8编译器目前预计会对类型推断进行许多改进.我不确定你的具体案例是否会得到解决.

那么,实际发生了什么?

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

  • 考虑捕获通配符(capture<? extends Foo>)的最佳方法是作为相同边界的未命名类型参数(即<T extends Foo>,但无法引用T).
  • "释放"捕获功能的最佳方法是将其绑定到泛型方法的命名类型参数.我将在下面的示例中演示这一点.请参阅Java教程"通配符捕获和帮助方法"(感谢参考@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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,这开始解释为什么消费者singletonList会从类型推断器解析受益TTest<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;
} 
Run Code Online (Sandbox Code Playgroud)

这有充分的理由不编译.如果确实如此,那么这将是可能的:

//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>
Run Code Online (Sandbox Code Playgroud)

那么为什么明确的工作呢?

让我们回到开头.如果以上是不安全的,那么为什么允许这样做:

List<Test<? extends Number>> l =
    Collections.<Test<? extends Number>>singletonList(t);
Run Code Online (Sandbox Code Playgroud)

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

Test<capture<? extends Number>> capturedT;
Test<? extends Number> t = capturedT;
Run Code Online (Sandbox Code Playgroud)

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

public static <T extends Number> Test<? extends Number> assign(Test<T> t) {
    return t;
} 
Run Code Online (Sandbox Code Playgroud)

这成功编译.并且不难看出为什么它应该是安全的.这是类似的用例

List<? extends Number> l = new List<Double>();
Run Code Online (Sandbox Code Playgroud)


Aff*_*ffe 8

没有潜在的运行时错误,它只是在编译器静态确定它的能力之外.每当您进行类型推断时,它会自动生成一个新的捕获<? extends Number>,并且两次捕获不被视为等效.

因此,如果通过指定<T>它来从singletonList的调用中删除推断:

List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t);
Run Code Online (Sandbox Code Playgroud)

它工作正常.生成的代码与您的调用是合法的没有什么不同,它只是编译器的一个限制,它无法自己解决这个问题.

推理创建捕获和捕获不兼容的规则是阻止本教程示例编译然后在运行时爆炸的原因:

public static void swap(List<? extends Number> l1, List<? extends Number> l2) {
    Number num = l1.get(0);
    l1.add(0, l2.get(0));
    l2.add(0, num);
}
Run Code Online (Sandbox Code Playgroud)

是的,语言规范和编译器可能会变得更加复杂,除了它之外,它可以说明你的例子,但事实并非如此,它很简单,可以解决.