为什么编译器更喜欢 int 重载而不是 char 的 varargs char 重载?

Why does the compiler prefer an int overload to a varargs char overload for a char?

代码

public class TestOverload {

    public TestOverload(int i){System.out.println("Int");}
    public TestOverload(char... c){System.out.println("char");}

    public static void main(String[] args) {
        new TestOverload('a');
        new TestOverload(65);
    }
}

输出

Int
Int

这是预期的行为吗?如果是这样,那为什么呢?我期待:char, Int

注:我用的是Java8

当编译器决定选择哪个重载方法时,具有可变参数 (...) 的方法具有最低的优先级。因此,当您使用单个 char 参数 'a' 调用 TestOverload 时,会选择 TestOverload(int i) 而不是 TestOverload(char... c),因为 char 可以自动提升为int.

JLS 15.12.2 :

  1. The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase. This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

  2. The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase. This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

  3. The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

编辑:

如果你希望强制编译器调用 TestOverload(char... c) 构造函数,你可以传递给构造函数调用 char[] :

new TestOverload (new char[] {'a'});

是的,这是预期的行为。方法调用的优先级是这样的:

  1. 加宽
  2. 拳击
  3. 可变参数

以下摘自 Java docs 相关内容:-

The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1).

The remainder of the process is split into three phases, to ensure compatibility with versions of the Java programming language prior to Java SE 5.0. The phases are:

The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

Joshua Bloch 的可靠建议(有效 Java,第 2 版):

"only choose as arguments for an overloaded method those that have -radically- different types."

类型完全不同的对象无法合理地转换为另一种参数类型。遵循此规则可能会为您节省调试神秘错误的时间,当编译器在编译时选择您没有预料到的方法重载时,可能会发生这种错误。

您的代码行违反了此规则并为错误打开了大门:

public TestOverload(int i){System.out.println("Int");}
public TestOverload(char... c){System.out.println("char");}

A char 可以与 int 相互转换,因此您可以预测调用会发生什么的唯一方法是转到 Java 语言规范并阅读一些内容关于如何解决重载的神秘规则。

幸运的是,这种情况应该不需要 JLS 研究。如果您的参数彼此之间没有根本不同,最好的选择可能是 不重载 。为方法指定不同的名称,以便可能需要维护代码的任何人都不会出现错误或混淆。

时间就是金钱。

我从 this link 中获取代码并修改了其中的一些部分:

    public static void main(String[] args) {
    Byte i = 5;
    byte k = 5;
    aMethod(i, k);
}

//method 1
static void aMethod(byte i, Byte k) {
    System.out.println("Inside 1");
}

//method 2
static void aMethod(byte i, int k) {
    System.out.println("Inside 2");
}

//method 3
static void aMethod(Byte i, Byte k) {
    System.out.println("Inside 3 ");
}

//method 4
static void aMethod(Byte  i, Byte ... k) {
    System.out.println("Inside 4 ");
}

编译器对方法 1、2 和 3 给出了错误(该方法对于类型重载不明确),但不是方法 4(为什么?)

答案在于 java 用于将方法调用与方法签名匹配的机制。该机制分三个阶段完成,在每个阶段如果找到匹配的方法就会停止:

+第一阶段:使用加宽寻找匹配方法(未找到匹配方法)

+阶段二:(也)使用boxing/unboxing寻找匹配方法(方法1,2和3匹配)

+阶段三:(也)使用 var args(方法 4 匹配!)