【问题标题】:Why are Java wildcards more powerful than use-site variance?为什么 Java 通配符比使用站点变化更强大?
【发布时间】:2014-07-17 15:20:26
【问题描述】:

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

那么这两者有什么区别呢?您能否举一个具体的例子,可以使用 Java 通配符但不能使用站点差异?

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

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

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

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

【问题讨论】:

  • 这和这个问题有什么不同吗? stackoverflow.com/questions/4231305/…
  • @IsaiahvanderElst:不,您链接的问题是关于声明站点差异(例如,C# 的out)与使用站点差异。我的问题是关于使用站点差异与通配符,这对我来说似乎完全相同。但是您链接的问题中的第一个答案至少说明了我的主张:“或者至少 Java 的通配符是(实际上比使用站点差异更强大)”
  • @IsaiahvanderElst:我之前评论的第一句话措辞不正确。我想说的是“是的,这个问题与您链接的问题完全不同。”希望解释清楚。
  • @gexicide:看起来给出的示例答案有误。这个人可能是说:...or at least Java's wildcards are (which are actually more powerful than declare-site variance).。 Java 通过通配符提供使用站点差异注释,所以我看不出一个应该比另一个更好。
  • @IsaiahvanderElst:我认为确实如此。这称为双变量。然而,在任何具有Object 类型(类型层次结构的根)的语言中,二元性等于covarianceObject。通配符也是如此,因为? 等于? extends Object

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


【解决方案1】:

在阅读了很多关于这个主题的内容后,我似乎在 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&lt;?&gt; 类型的对象作为 swapLastTwo 方法的 value 参数,要传递的类型参数 对于E 不能手动指定,因为? 隐藏的类型不能 由程序员命名。但是,通过 Stack&lt;?&gt; 类型检查 因为 Java 允许编译器自动为 未知类型(捕获转换)并在方法中使用此名称 调用。

即,在 Java 中,我们可以使用 Stack&lt;?&gt; 作为输入参数来调用 swapLastTwo()。然后编译器将? 捕获到类型变量E 中,因此知道我们可以在刚刚poped 的元素上调用push。由于使用地点的差异,我们无法做到这一点,因为类型系统会丢失从pop 返回的元素属于push 预期的类型的信息。

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

例如,这不能在 Java 中编译:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-15
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2016-05-07
    • 2018-08-20
    相关资源
    最近更新 更多