为什么此方法重载不明确?

2022-09-01 23:54:01
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);
    }
}

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


答案 1

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

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

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

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

考虑有关

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

对于 JLS 7,它声明

当且仅当以下所有条件都成立时,方法 m 是适用的可变 arity 方法

  • 对于 1 = i < n,ei 的类型 Ai 可以通过方法调用转换为 Si。
  • ...

(其他条件是指此处不相关的调用形式,例如真正使用 varargs 的调用,或涉及泛型的调用)

参考示例:当可以通过方法调用转换转换为相应的形式化方法参数时,方法适用于类型的实际参数表达式。根据JLS7中有关方法调用转换的相应部分,允许以下转换:bByteb

  • 身份转换 (§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一起使用。但是,在 JLS 8 中,变量 arity 调用的识别方法中变量 arity 调用的适用性定义发生了微小的变化:

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

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


顺便说一句,再次提到你的陈述,你“......发现扩大优先级高于取消装箱“:对于涉及 varargs(并且根本不需要方法调用转换)的方法,情况确实如此。如果您在示例中省略了 varags,则查找匹配方法的过程将从阶段 1:识别匹配 Arity 方法适用于子类型开始。然后,该方法已经适用于该参数,因为它是 的子类型。没有理由进入阶段2:识别方法调用转换适用的匹配Arity方法。在此阶段中,将通过从 到 取消装箱的方法调用转换将适用,但从未达到此阶段。m(Number b)Byte bByteNumberBytebyte


答案 2