【问题标题】:Why is this method overloading ambiguous?为什么这个方法重载模棱两可?
【发布时间】:2014-04-11 18:53:38
【问题描述】:
public class Primitive {
    void m(Number b, Number ... a) {} // widening, autoboxing->widening->varargs

    void m(byte b, Number ... a) {} // unboxing, autoboxing->widening->varargs

    public static void main(String[] args) {
        Byte b = 12;
        Primitive obj = new Primitive();
        obj.m(b, 23);
    }
}

我已经搜索了,发现拉宽优先级高于拆箱,所以在上面的方法调用中,应该调用第一个方法,因为第二个参数对于两者都是相同的。但这不会发生。能解释一下吗?

【问题讨论】:

  • 它为我编译 - 你使用的是哪个编译器? (哪个版本?)
  • 这些优先级在独立的步骤中发生。首先是直接,然后是加宽,然后是拆箱。
  • @Sotirious Delimanolis 你能详细说明你的答案吗
  • 这无法在 IntelliJ 中编译,但可以在 javac 中正常工作。 JDK 1.8 版
  • 无法在 JDK 7 too 上编译。似乎是 Java 8 中已修复的错误。

标签: java overloading wrapper primitive jdk1.6


【解决方案1】:

它无法在 JDK 1.5、1.6 和 1.7 中编译,但在 JDK 1.8 中有效。

更新:似乎它与第一个 JDK8 版本一起工作的事实实际上是一个错误:它在 JDK 1.8.0_05 中工作,但根据this question 和 medvedev1088 的回答,此代码将不再在 1.8.0_25 中编译,这是符合 JLS 的行为

我不认为这是一个已修复的错误。相反,它是与 Java 8 中 lambda 表达式的方法调用机制相关的更改的影响。

大多数人可能会同意,关于“方法调用表达式”的部分是迄今为止 Java 语言规范中最复杂难以理解的部分。并且可能有一整个工程师团队负责交叉检查和验证这一部分。因此,任何陈述或任何尝试的推理都应谨慎对待。 (即使它来自上述工程师)。但我会试一试,至少充实其他人可能会参考以进行进一步分析的相关部分:

考虑到关于的部分

考虑到这两种方法都是“可能适用的方法”(JLS7/JLS8),那么相关的小节就是关于

对于 JLS 7,它声明

方法 m 是一个适用的可变参数方法当且仅当以下所有条件都成立:

  • 对于1 = i
  • ...

(其他条件是指与此处无关的调用形式,例如真正使用可变参数的调用,或涉及泛型的调用)

参考示例:当b可以通过方法调用转换转换为相应的形式方法参数时,一个方法适用于Byte类型的实际参数表达式b。根据JLS7中关于Method Invocation Conversion的相应部分,允许进行以下转换:

  • 身份转换 (§5.1.1)
  • 扩大的基元转换 (§5.1.2)
  • 扩大参考转换 (§5.1.5)
  • 装箱转换(第 5.1.7 节)可选地后跟扩大参考转换
  • 拆箱转换(第 5.1.8 节)可选地后跟加宽基元转换。

显然,根据本规范,有两种方法适用:

  • m(Number b, Number ... a) 通过扩大引用转换适用
  • m(byte b, Number ... a) 通过拆箱转换适用

您提到您“...发现扩大优先级高于拆箱”,但这不适用于此处:上面列出的条件不涉及任何“优先级”。它们被列为不同的选项。即使第一种方法是void m(Byte b, Number ... a),“身份转换”也适用,但它仍然只能算作一次可能的转换,并且由于歧义而导致错误方法。


所以,据我所知,这解释了为什么它与 JDK7 一起工作。我没有详细弄清楚为什么它确实可以与 JDK8 一起使用。但是在Identify Methods Applicable by Variable Arity Invocation in JLS 8 中对可变参数方法适用性的定义略有改变:

如果 m 不是泛型方法,则 m 可通过变量 arity 调用适用,如果对于 1 ≤ i ≤ k,ei 在松散调用上下文中与 Ti 兼容,或者 ei 与适用性无关(第 15.12 节)。 2.2)。

(我还没有深入研究“松散调用上下文”的定义和第 15.12.2.2 节,但这似乎是这里的关键区别)


顺便说一句,再次提到您的陈述,即您“......发现扩大优先级高于拆箱”:对于那些的方法来说,这是正确的涉及可变参数(并且根本不需要方法调用转换)。如果您在示例中省略了可变参数,则查找匹配方法的过程将从Phase 1: Identify Matching Arity Methods Applicable by Subtyping 开始。由于ByteNumber 的子类型,因此m(Number b) 方法将已经适用于参数Byte b。没有理由进入Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion。在这个阶段,通过从Bytebyte的拆箱的方法调用转换将适用,但永远不会到达这个阶段。

【讨论】:

  • @medvedev1088 我仍然没有研究与“调用上下文”等相关的更新细节。但是现在,根据您链接到的问题(和您的答案),它似乎 就像这里的代码确实 与 JDK8 一起工作是早期 JDK8 版本中的一个错误,并且这个错误在版本 8-25 中已修复 - 你同意吗?跨度>
  • 是的,我同意。刚刚在 JDK 1.8.0_25 上测试过,它不能编译。而且它不应该根据 JLS,因为编译器无法确定哪种方法更具体。它还提出了一个重要问题:JLS 是否应该改进以及如何改进。一些编译器错误是违反直觉的,例如我链接的问题中的错误。你怎么看?
  • @medvedev1088 上面的回答中提到:JLS的这一部分可能是最复杂的,在这方面“改进”JLS是困难的。任何简化它的尝试都可能为歧义打开大门,另一方面,试图使其更清晰并指出它目前所说的某些含义可能让它变得更加复杂。 (而且我们在这里仍然在谈论“琐碎”的案例:真正 令人讨厌的部分只有在涉及泛型时才会发挥作用!)。所以我不知道该怎么做。 (顺便说一句:我用另一个链接更新了这个答案)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-06
  • 2013-06-21
相关资源
最近更新 更多