Varargs Java 模棱两可的调用

Varargs Java Ambiguous Call

我对 Java 的 varargs 方法有点困惑:

public static int sum(int ...a) {
    return 0;
}

public static double sum(double ...a) {
    return 0.0;
}

当我试图在不传递任何参数的情况下调用 sum() 时,会调用 int 版本的方法。我不明白为什么;通常编译器必须引发错误。

相比之下,当我尝试不带任何参数调用 sum 时,以下代码会生成编译器错误:

public static int sum(int ...a) {
    return 0;
}

public static boolean sum(boolean ...a) {
    return true;
}

此处适用的一般规则是:如果一个方法签名比另一个严格更具体,则Java选择它不会出错。

直觉上,如果您可以完全删除方法签名,则方法签名会更具体,而另一个不太具体的方法签名将适用于每个现有调用。

当在签名 sum(int... args)sum(double... args) 之间做出选择时,签名 sum(int... args) 更具体,因为对该方法的任何调用也可以传递给 sum(double... args) 通过应用扩大转换。对于无法进行类似转换的 sum(boolean... args) 方法,情况并非如此。

Java 语言规范,SE 8 版本:

15.12. Method Invocation Expressions

15.12.2.5. Choosing the Most Specific Method

The Java programming language uses the rule that the most specific method is chosen.

...

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

...

  • m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

...

A type S is more specific than a type T for any expression if S <: T (§4.10).


4.10. Subtyping

4.10.1. Subtyping among Primitive Types

double >1 float

float >1 long

long >1 int

正如this answer中提到的,在选择使用哪种重载方法时要遵循一些规则。

引用:

  1. Primitive widening uses the smallest method argument possible
  2. Wrapper type cannot be widened to another Wrapper type
  3. You can Box from int to Integer and widen to Object but no to Long
  4. Widening beats Boxing, Boxing beats Var-args.
  5. You can Box and then Widen (An int can become Object via Integer)
  6. You cannot Widen and then Box (An int cannot become Long)
  7. You cannot combine var-args, with both widening and boxing.

(让我们像这样重新定义规则 1:"Primitive widening uses the most specific method argument as possible.")

因此,牢记这些规则,我们可以了解这里发生的事情:

根据第一条规则,基元扩展尽可能使用最具体的方法参数。由于 int 由非十进制数表示(例如 1),而 double 由十进制数表示,精度比 [=13= 高 32 个字节](例如 1.0),我们可以说 ints 是 "less than" 或 "smaller than" doubles,按照这个逻辑,ints可以是 "promoted" 到 doubles 和 doubles 可以是 "demoted" 到 ints.

简而言之,可以扩展为另一个基元的基元(例如 int -> float -> double更具体 比另一个。例如,intdouble 更具体 因为 1 可以提升为 1.0.

当您没有向这些重载的同名可变参数方法传递任何参数时,由于 return 实际上是相同的(分别为 0 和 0.0),编译器将选择使用接受的方法int 类型的可变参数,因为它 更具体 .

那么,当您引入这些分别采用 ints 和 booleans(不能相互扩展的类型)的相同方法时,编译器现在无法选择方法使用,因为 ints 不能像 ints、floats 和 doubles 那样是 "promoted" 或 "demoted"。因此,它会抛出一个编译错误。

我希望这可以帮助您了解正在发生的事情。