java - 为什么此方法重载模棱两可?

标签 java overloading wrapper primitive jdk1.6

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);
    }
}

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

最佳答案

它无法在JDK 1.5、1.6和1.7中进行编译,但可以在JDK 1.8中使用。

Update: It seems like the fact that it worked with the first JDK8 versions was actually a bug: It worked in JDK 1.8.0_05, but according to this question and the answer by medvedev1088, this code will no longer compile in 1.8.0_25, which is the behavior that conforms to the JLS


我认为这不是已修复的错误。相反,它是与Java 8中lambda表达式的方法调用机制相关的更改的影响。
大多数人可能会同意,关于“方法调用表达式”的部分是迄今为止Java语言规范中最复杂,最难以理解的部分。可能会有整个工程师团队负责交叉检查和验证此部分。因此,任何陈述或任何试图进行的推理都应花费大量的盐。 (即使来自上述工程师)。但我会尝试一下,至少充实其他人可能会引用以进行进一步分析的相关部分:
考虑有关

JLS 7中的
  • Method Invocation Expressions
  • JLS 8中的
  • Method Invocation Expressions

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

    JLS7中的
  • Phase 3: Identify Applicable Variable Arity Methods
  • JLS8中的
  • Phase 3: Identify Methods Applicable by Variable Arity Invocation

  • 对于JLS 7,它指出

    The method m is an applicable variable-arity method if and only if all of the following conditions hold:

    • For 1 = i < n, the type of ei, Ai, can be converted by method invocation conversion to Si.
    • ...

    (其他条件指的是此处不相关的调用形式,例如真正使用varargs的调用或涉及泛型的调用)
    参见示例:当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中,可变Arity方法的适用性定义发生了明显变化:

    If m is not a generic method, then m is applicable by variable arity invocation if, for 1 ≤ i ≤ k, either ei is compatible in a loose invocation context with Ti or ei is not pertinent to applicability (§15.12.2.2).


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

    顺便说一句,再次提到您“...发现扩展优先级高于拆箱优先级”的声明:对于执行而不是涉及vararg的方法(这完全不需要方法调用转换)来说,这是正确的。如果在示例中省略了变量,那么查找匹配方法的过程将从Phase 1: Identify Matching Arity Methods Applicable by Subtyping开始。由于m(Number b)Byte b的子类型,因此Byte方法将已经适用于参数Number。没有理由进入Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion。在此阶段,将应用通过从Bytebyte取消装箱的方法调用转换,但是此阶段从未实现。

    关于java - 为什么此方法重载模棱两可?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23020493/

    相关文章:

    java - 如何在 IntellIJ 控制台 (2019.3) 中使用 ANSI 颜色输出?

    java - 如何在Java中使用Unix纪元每分钟打印数据

    java - 使用JAVA API从Elastic Search建议搜索响应中提取源数据

    c++ - 根据抽象工厂中可用的重载版本自动选择构造函数

    c# - 使用 CLR C++ 包装器从 C# 应用程序在 C++ 中加载 SDL 窗口不起作用

    java - 最长递增子序列 - 无法理解实际的 LIS 创建

    c# - 如何正确地覆盖平等?

    python - 动态重载运算符

    c - Swift 中的任意 C 回调包装器

    java - JNI GetMethodID 不适用于内部类的构造函数