25

情况1

static void call(Integer i) {
    System.out.println("hi" + i);
}

static void call(int i) {
    System.out.println("hello" + i);
}

public static void main(String... args) {
    call(10);
}

案例 1 的输出:hello10

案例2

static void call(Integer... i) {
    System.out.println("hi" + i);
}

static void call(int... i) {
    System.out.println("hello" + i);
}

public static void main(String... args) {
    call(10);
}

显示编译错误reference to call ambiguous。但是,我无法理解。为什么 ?但是,当我从 中注释掉任何call()方法时Case 2,它工作正常。谁能帮我理解,这里发生了什么?

4

6 回答 6

15

查找最具体的方法在 Java 语言规范 (JLS) 中以非常正式的方式定义。我在尝试尽可能多地删除正式公式的同时提取了以下适用的主要项目。

总之,适用于您的问题的主要项目是:

第三阶段(第 15.12.2.4 节)允许将重载与可变参数方法、装箱和拆箱相结合。

  • 那么JLS 15.12.2.4基本确定这两种方法都适用,因为 10 可以同时转换为 anInteger...或 an int...。到现在为止还挺好。该段的结论是:

最具体的方法(§15.12.2.5)是在适用的可变参数方法中选择的。

  • 这将我们带到了JLS 15.12.2.5。本段给出了一种arity方法m(a...)比另一种arity方法更具体的条件m(b...)。在您使用一个参数且没有泛型的用例中,它归结为:

m(a...)m(b...)iif更具体a <: b,其中<:表示is a subtype of.

它碰巧int不是 的子类型,Integer也不Integer是 的子类型int

因此,要使用 JLS 语言,这两种call方法都具有最大的特异性(没有一种方法比另一种更具体)。在这种情况下,同一段得出结论:

  • 如果所有最具体的方法都具有覆盖等效(第 8.4.2 节)签名 [...] => 不是您的情况,因为不涉及泛型并且 Integer 和 int 是不同的参数
  • 否则,我们说方法调用不明确,出现编译时错误。

笔记

如果您替换Integer...long...例如,您将拥有int <: long并且最具体的方法将是call(int...)*.
同样,如果您替换int...Number...,则该call(Integer...)方法将是最具体的。

*在 Java 7 之前的 JDK中实际上存在一个错误,在这种情况下会显示出模棱两可的调用。

于 2013-01-02T14:56:42.427 回答
4

看起来它与错误 #6886431有关,这似乎已在 OpenJDK 7 中修复。

以下是错误描述,

错误描述:

调用具有以下重载签名的方法时,我预计会出现歧义错误(假设参数与两者兼容):

int f(Object... args);
int f(int... args);

javac 将第二个视为比第一个更具体。这种行为是明智的(我更喜欢它),但与 JLS(15.12.2)不一致。

于 2012-12-27T11:17:01.980 回答
2

JLS 15.12.2.2

JLS 15.12.2.2 选择最具体的方法

I如果多个方法声明既可访问又适用于方法调用,则必须选择一个为运行时方法分派提供描述符。Java 编程语言使用选择最具体方法的规则。非正式的直觉是,如果第一个方法处理的任何调用可以传递给另一个方法而没有编译时类型错误,那么一个方法声明比另一个方法声明更具体。

这些方法都不能传递给另一个(int[] 和 Integer[] 的类型不相关),因此调用不明确

于 2012-12-27T11:40:55.787 回答
1

编译器不知道应该调用哪个方法。为了解决这个问题,您需要转换输入参数..

public static void main(String... args) {
  call((int)10);
  call(new Integer(10));
}

编辑:

这是因为编译器试图将 Integer 转换为 int,因此,在调用方法之前会发生隐式转换call。因此,编译器随后会查找具有该名称且可以采用整数的任何方法。你有 2 个,所以编译器不知道应该调用哪一个。

于 2012-12-27T11:07:09.343 回答
0

如果可以应用不止一种方法,那么从 Java 语言规范中我们选择最具体的方法,段落15.12.2.5

一个名为m的变量 arity 成员方法比另一个同名的变量 arity 成员方法更具体,如果 ( <: means subtyping):

  1. 一个成员方法有 n 个参数,另一个有 k 个参数,其中 n ≥ k,并且:
    • 第一个成员方法的参数类型为T1, ..., Tn-1, Tn[]。(我们只有一个 T_n[],即 Integer[], n=1
    • 其他方法的参数类型为U1, ..., Uk-1, Uk[]。(同样只有一个参数,即 int[], k=1
    • 如果第二种方法是泛型的,则令 R1 ... Rp (p ≥ 1) 为其类型参数,令 Bl 为 Rl (1 ≤ l ≤ p) 的声明边界,令 A1 ... Ap 为推断的类型参数(§15.12.2.7) 在初始约束 Ti << Ui (1 ≤ i ≤ k-1) 和 Ti << Uk (k ≤ i ≤ n) 下进行此调用,并让 Si = Ui[R1=A1,。 ..,Rp=Ap] (1 ≤ i ≤ k)。(方法不是通用的
    • 否则,令 Si = Ui (1 ≤ i ≤ k)。( S1 = int[] )
    • 对于从 1 到 k-1 的所有 j,Tj <: Sj,并且,(这里什么都没有
    • 对于从 k 到 n 的所有 j,Tj <: Sk,并且,(比较 T1 <: S1, Integer[] <: int[]
    • 如果第二种方法是如上所述的通用方法,则 Al <: Bl[R1=A1,...,Rp=Ap] (1 ≤ l ≤ p)。(方法不是通用的

尽管原语int被自动装箱为 wrapper Integerint[]但未自动装箱为Integer[],但第一个条件不成立。

第二个条件几乎相同。

还有其他条件不成立,再由于JLS:

我们说方法调用不明确,出现编译时错误。

于 2012-12-27T11:50:07.393 回答
0

这个问题已经被问过很多次了。棘手的部分是f(1, 2, 3)显然是通过int's,那么为什么编译器不能选择f(int...)版本呢?答案必须在JLS中的某个地方,我正在摸不着头脑

根据 §15.12.2.4,这两种方法都适用于 variable-arity method,因此下一步是确定最具体的方法。

不幸的是,§15.12.2.5使用子类型测试T i <: S if1(T 1 , .. T n )f2(S 1 , .. S n )形式参数之间来识别目标方法,并且由于存在Integer和之间没有子类型关系int,没有人获胜,因为既不是int :> Integer也不是Integer:> int。在该段的末尾指出:

上述条件是唯一一种方法可能比另一种方法更具体的情况。[...]

当且仅当 m1 比 m2 更具体且 m2 不比 m1 更具体时,方法 m1严格比另一种方法 m2 更具体。

如果一个方法是可访问和适用的,并且没有其他严格更具体的适用和可访问的方法,则该方法被称为对方法调用具有最大的特定性。

可能没有一种方法是最具体的,因为有两种或多种方法是最具体的。在这种情况下:

  1. [...]

  2. 否则,我们说方法调用不明确,出现编译时错误。

附上Gilad Bracha的一篇博客文章(见附件 2),该文章又链接到 @Jayamhona 答案的错误报告中。

于 2012-12-27T12:06:10.500 回答