为什么Java通配符比使用站点方差更强大?

gex*_*ide 5 java generics types type-systems type-theory

我经常读到Java通配符是一个比使用站点方差概念更强大的概念.但在我的理解中,Java通配符的概念与使用站点方差的概念完全相同.

那两者有什么区别?您能给出Java通配符可能的具体示例,但不能使用站点差异吗?

例如,Java的使用站点差异如何与C#的声明站点差异进行比较的第一个答案 是我的问题的主张的一个例子:

首先,如果我没记错的话,使用站点方差比声明站点方差更严格(尽管以简洁为代价),或者至少是Java的通配符(实际上比使用站点方差更强大).

但是,答案并没有说明有什么区别,只有有一个区别.

编辑: 我在这里找到的第一个区别(第112页的第一段)似乎是使用站点方差完全不允许调用一个类型参数位于错误位置的方法,而通配符允许调用某些类型.例如,你不能打电话add,上List<? extends String>.使用Java通配符,您可以调用add这样的类,但必须使用null作为参数.对于逆变,可以调用任何返回逆变参数的方法,但必须假设返回类型为Object.但这是唯一的区别吗?

gex*_*ide 5

在这个题目读了很多之后,我似乎找到了答案这个文件Altidor,赖兴巴赫和Smaragdakis的.Java泛型与使用站点差异形成对比的主要附加功能是捕获转换,它允许在类型参数中捕获先前未知类型的通配符.本文中的这个例子最好地解释了:

一个复杂因素是Java通配符不仅仅是使用站点方差,还包括受存在类型机制启发的机制.通配符捕获是将通配符隐藏的未知类型作为方法调用中的类型参数传递的过程.考虑以下方法,该方法将堆栈顶部的两个元素的顺序交换.

  <E> void swapLastTwo(Stack<E> stack) { 
        E elem1 = stack.pop();
        E elem2 = stack.pop();
        stack.push(elem2); 
        stack.push(elem1); 
   }
Run Code Online (Sandbox Code Playgroud)

虽然程序员可能希望将类型的对象Stack<?>作为值参数传递给swapLastTwo方法,但是E无法手动指定要传递的类型参数,因为?程序员无法命名隐藏的类型.但是,传递Stack<?>类型检查是因为Java允许编译器自动生成未知类型的名称(捕获转换)并在方法调用中使用此名称.

即,在Java中,我们可以swapLastTwo()使用Stack<?>输入参数进行调用.然后编译器捕获?到一个类型变量E,因此知道我们可以调用push我们刚刚编写的元素pop.使用站点方差,我们无法做到这一点,因为类型系统会丢失返回的元素pop属于push预期类型的信息.

请注意,我们必须使用类型参数来捕获类型.不这样做会使类型检查器将从pop不同类型返回的类型视为push预期的类型.

例如,这不能用Java编译:

Stack<?> s = ...;
s.push(s.pop());
Run Code Online (Sandbox Code Playgroud)

这里,元素的类型s将被捕获​​到两个不同的新类型变量中(在eclipse中调用capture 1 of ?和调用capture 2 of ?.类型检查器将这些类型变量视为不同,代码不会编译.通过使用泛型方法,我们可以捕获类型?到一个允许调用pushpop.

我不确定这是Java通配符和"通常"(无论是什么)使用站点方差之间的唯一区别,但至少它似乎是一个非常显着的差异.