我经常读到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
。但这是唯一的区别吗?
答案 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 ?
。类型检查器将这些类型变量视为不同并且代码不能编译。通过使用泛型方法,我们可以将?
的类型捕获到一个允许调用push
和pop
的命名参数中。
我不确定这是Java通配符和“通常”(无论是什么)使用站点方差之间的唯一区别,但至少它似乎是一个非常显着的差异。