编译器错误:引用调用模糊

时间:2012-12-27 11:02:29

标签: java methods polymorphism java-7 boxing

案例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方法时,它的工作正常。任何人都可以帮助我理解,这里发生了什么?

6 个答案:

答案 0 :(得分:14)

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

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

  

第三阶段(§15.12.2.4)允许重载与变量arity方法,装箱和拆箱相结合。

  • 然后JLS 15.12.2.4基本上确定两种方法都适用,因为10可以转换为Integer...int...。到现在为止还挺好。该段的结论是:
  

在适用的变量方法中选择最具体的方法(第15.12.2.5节)。

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

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

恰好int不是Integer的子类型而Integer不是int的子类型。

要使用JLS语言,因此两种call方法都是最具体的(没有方法比另一种方法更具体)。在这种情况下,同一段落结束:

  
      
  • 如果所有最大特定方法都具有覆盖等效(§8.4.2)签名[...] =&gt;不是你的情况,因为没有涉及泛型,Integer和int是不同的参数
  •   
  • 否则,我们说方法调用不明确,并且发生编译时错误。
  •   

注意

例如,如果您将Integer...替换为long...,则会int <: long,而最具体的方法是call(int...) *。
同样,如果您将int...替换为Number...,则call(Integer...)方法将是最具体的。

*实际上有a bug in JDKs prior to Java 7 that would show an ambiguous call in that situation

答案 1 :(得分:4)

看起来它与bug #6886431有关,似乎在OpenJDK 7中修复了。

以下是错误说明,

错误说明:

  

当调用具有以下重载签名的方法时,我   期望模糊错误(假设参数与...兼容)   两者):

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

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

答案 2 :(得分:2)

来自JLS 15.12.2.2

  

JLS 15.12.2.2选择最具体的方法

     

IIf多个方法声明既可访问又适用   要进行方法调用,有必要选择一个来提供   运行时方法调度的描述符。 Java编程   language使用选择最具体方法的规则。该   非正式的直觉是一种方法声明更具体   如果第一种方法处理的任何调用都可以,那么   传递给另一个没有编译时类型错误。

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

答案 3 :(得分:1)

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

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

编辑:

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

答案 4 :(得分:0)

如果可以应用多种方法,那么来自Java语言规范我们Choosing the Most Specific Method,段落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)为其类型参数,设B1为Rl的声明界限(1≤l≤p),让A1 ... Ap为在初始约束条件下对此调用推断出类型参数(第15.12.2.7节)Ti&lt;&lt; Ui(1≤i≤k-1)和Ti <&lt; 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&lt;:Sj,和(没有在这里
    • 对于从k到n的所有j,Tj&lt;:Sk,和,(比较T1&lt;:S1,Integer []&lt ;: int []
    • 如果第二种方法是如上所述的通用方法,则Al&lt;:Bl [R1 = A1,...,Rp = Ap](1≤l≤p)。 (方法不通用
  2. 虽然原始int已自动装箱到包装Integer,但int[]未自动装箱到Integer[],而不是第一个条件不能保留。

    第二个条件几乎相同。

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

      

    我们说方法调用是不明确的,并且发生编译时错误。

答案 5 :(得分:0)

这个问题has already been asked多次。棘手的部分是f(1, 2, 3)显然正在传递int,所以为什么编译器不能选择f(int...)版本?答案必须在JLS的某处,我正在摸不着头脑。

根据§15.12.2.4,两种方法都是适用的变量方法,因此下一步是确定最具体的方法。

不幸的是,§15.12.2.5使用 f1之间的子类型测试 T i &lt;:S i (T < sub> 1 ,.. T n f2(S 1 ,.. S n 用于标识目标方法的形式参数,并且由于Integerint之间没有子类型关系,因此没有人获胜,因为 int:&gt;整数整数:&gt; INT 。在该段末尾陈述:

  

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

     

方法m1 严格更具体,而不是另一种方法m2,如果   并且只有当m1比m2更具特异性且m2不再具体时   比m1。

     

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

     

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

     
      
  1. [...]

  2.   
  3. 否则,我们说方法调用不明确,并且发生编译时错误。

  4.   

Gilad Bracha附上a blog post(见图2),反过来联系@ Jayamhona答案的错误报告。