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

时间:2014-07-17 15:20:26

标签: java generics types type-systems type-theory

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

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

例如,How does Java's use-site variance compare to C#'s declaration site variance?中的第一个答案就是我提出问题的一个例子:

  

首先,如果我没记错,使用网站差异严格来说更多   比声明站点差异更强大(尽管代价是   简洁),或者至少是Java的通配符(实际上更多   比使用场地差异更强大)。

然而,答案并未说明有什么区别,只有有一个区别。

修改 我发现here(第112页的第一段)的第一个区别似乎是,使用站点方差完全不允许调用类型参数位于错误位置的方法,而通配符允许使用某些类型调用它。例如,您无法在add上致电List<? extends String>。使用Java通配符,可以在这样的类上调用add,但您必须使用null作为参数。对于逆转,一个可以调用任何返回逆变参数的方法,但必须假设返回类型为Object。但这是唯一的区别吗?

1 个答案:

答案 0 :(得分:5)

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

  

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

  <E> void swapLastTwo(Stack<E> stack) { 
        E elem1 = stack.pop();
        E elem2 = stack.pop();
        stack.push(elem2); 
        stack.push(elem1); 
   }
     

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

即,在Java中,我们可以使用swapLastTwo()作为输入参数调用Stack<?>。然后,编译器将?捕获到类型变量E中,因此知道我们可以在push编辑的元素上调用pop。使用站点差异时,我们无法执行此操作,因为类型系统会丢失从pop返回的元素属于push预期类型的​​信息。

请注意,我们必须使用类型参数来捕获类型。不这样做会使类型检查器将pop返回的类型与push预期的类型区别对待。

例如,这不能用Java编译:

Stack<?> s = ...;
s.push(s.pop());

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

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