当向方法传递参数时在 Java 中变得不明确?

When passing arguments to methods become ambiguous in Java?

当方法重载完成后,我知道我们只能创建具有相同名称的方法,前提是它们的方法签名不同。

class Demo{
    public static void myMethod(int y, double x){}
    public static void myMethod(double x,int y){}

    public static void main(String args[]){
        byte b=10;
        myMethod(b,b);
    }
}

上面显示的代码给出了一个错误 error: reference to myMethod is ambiguous 出现这个问题是因为byte值自动转换后可以赋给int和double两种类型,不知道该传给哪个方法对不对? 如有错误请指正..

我尝试了以下程序。我以为这也会报错,但编译没有报错

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        MyClass.myMethod(100);
    }
}

我认为它也会给出与之前相同的错误,但这给出了输出 myMethod(int)... 所以我假设因为它有一个完美匹配的方法可以传递int值,不会报错..

但是如果我对上面的第二个程序进行以下更改,为什么它不报错呢??

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        byte b=10;
        MyClass.myMethod(b);
    }
}

byte可以自动转成int和double吧?输出为 myMethod(int).. 这不应该让编译器感到困惑并给出错误 reference to myMethod is ambiguous??

要理解这类问题,你应该记住以下三点:

编译器总是尝试选择最具体的可用方法,对参数的修改次数最少。

Java 设计人员已决定旧代码的工作方式应与 boxing-unboxing 功能可用之前的工作方式完全相同。

加宽优于 boxing/unboxing(由于上述原因),而后者又优于 var-args。

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        MyClass.myMethod(100);
    }
}

输出是 myMethod1(int),因为 java 整数文字的默认类型是整数。所以它需要最接近的匹配并调用 myMethod(int i).

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        byte b=10;
        MyClass.myMethod(b);
    }
}

这不会给编译器造成歧义,因为您发送的参数是字节,字节被加宽以与方法参数匹配。如果你声明 myMethod(short i),它会调用这个而不是 myMethod(int i)。这就是 java.

中加宽的工作原理

byte->short->int->long

class Demo{
    public static void myMethod(int y, double x){}
    public static void myMethod(double x,int y){}

    public static void main(String args[]){
        byte b=10;
        myMethod(b,b);
    }
}

编译器在上面的代码片段中发现了一个歧义,因为 byte b 被扩展为 int 现在这两个方法的参数声明都为 (double,int) (int,double) 所以两个声明都存在歧义有一个 int 变量,因此它给编译器本身造成了混乱。尝试在声明中将其中一个方法声明更改为 (double,double) and you'll see that it calls the one that hasint`。我所说的例子是

class Demo{
    public static void myMethod(double y, double x){}
    public static void myMethod(double x,int y){}

    public static void main(String args[]){
        byte b=10;
        myMethod(b,b);
    }
}

在这种情况下,它将调用 myMethod(double x, int y)

更多清关可以参考JLS

它知道更喜欢升级到 int 而不是升级到 double,但是你已经让它在两种都需要升级到 int 的方法之间进行选择,所以它不知道你指的是哪一个。

我不会指定和详细说明编译器用来决定必须调用哪个方法的所有规则,因为它包含其他条件。 我将只关注您的问题的方法参数标准。

15.12.2.5. Choosing the Most Specific Method,你有一个宝贵的信息:

非正式的直觉是,如果第一个方法处理的任何调用都可以传递给另一个方法而没有 compile-time 类型错误,则一个方法比另一个方法更具体。

在您的示例中,这里是 JLS 中应该回答您的问题的部分:

One fixed-arity member method named m is more specific than another member method of the same name and arity if all of the following conditions hold:

  • The declared types of the parameters of the first member method are T1, ..., Tn.

  • The declared types of the parameters of the other method are U1, ..., Un.

  • If the second method is generic, then let R1 ... Rp (p ≥ 1) be its type parameters, let Bl be the declared bound of Rl (1 ≤ l ≤ p), let A1 ... Ap be the type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ti << Ui (1 ≤ i ≤ n), and let Si = Ui[R1=A1,...,Rp=Ap] (1 ≤ i ≤ n).

    Otherwise, let Si = Ui (1 ≤ i ≤ n).

  • For all j from 1 to n, Tj <: Sj.

  • If the second method is a generic method as described above, then Al <: Bl[R1=A1,...,Rp=Ap] (1 ≤ l ≤ p).

您应该感兴趣的是 For all j from 1 to n, Tj <: Sj

在 compile-time,如果确定了几种适用的方法,则选择最具体的一种。
然而,如果不止一种方法对有效参数类型具有最大特异性,编译器就不知道应该调用哪个方法。所以它会发出编译错误。

您可以在 JLS : 15.12.2. Compile-Time Step 2: Determine Method Signature:

中找到此信息

A method is said to be maximally specific for a method invocation if it is accessible and applicable and there is no other method that is applicable and accessible that is strictly more specific.

If there is exactly one maximally specific method, then that method is in fact the most specific method; it is necessarily more specific than any other accessible method that is applicable. It is then subjected to some further compile-time checks as described in §15.12.3.

It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:

- If all the maximally specific methods have override-equivalent (§8.4.2) signatures, then:

  • If exactly one of the maximally specific methods is not declared abstract, it is the most specific method.

  • Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type.

  • However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.

- Otherwise, we say that the method invocation is ambiguous, and a compile-time > error occurs.

如果我们将这些规则应用于您的三个示例(我更改了顺序,以编译良好的情况开始,以编译错误的情况结束)。

1) 找到一种最具体的方法

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        MyClass.myMethod(100);
    }
}

编译器看到一个完美匹配的方法:myMethod(int i) 因为传递的值是 int。它编译得很好。

2) 找到一种最具体的方法

class MyClass{
    public static void myMethod(int i){
        System.out.println("myMethod1(int)");
    }
    public static void myMethod(double a){
        System.out.println("myMethod2(int)");
    }   
}

class Demo{
    public static void main(String args[]){
        byte b=10;
        MyClass.myMethod(b);
    }
}

编译器发现一个方法比另一个方法具有更高的特异性。
根据扩大的原始转换规则,从 byteint 的隐式转换确实比从 bytedouble 的隐式转换更具体。

我们可以检查 void myMethod(int i) 是否比 void myMethod(double a) 更具体,如果第一个方法处理的任何调用都可以传递给另一个方法而没有 compile-time 类型错误。

myMethod(3); 应用于 void myMethod(double x) 编译正常。
但是 myMethod(double)3); 应用于 void myMethod(int y) 会产生编译错误。
所以找到了一个独特的最大特定方法:void myMethod(int i).
编译没问题

3) 未找到唯一最大特定方法

class Demo{
    public static void myMethod(int y, double x){}
    public static void myMethod(double x,int y){}

    public static void main(String args[]){
        byte b=10;
        myMethod(b,b);
    }
}

编译器看到两种方法,其中没有一种比另一种具有更高的特异性。
首先,在这两种情况下,都需要将有效参数类型隐式转换为方法参数的声明类型。
但在这两种情况下,特异性是相同的。

如果 any 方法处理的任何调用在没有 compile-time 类型错误的情况下无法传递给另一个调用,我们可以检查 void myMethod(int y, double x) 是否与 myMethod(double x,int y) 一样具体。

myMethod(double)3,4); 应用于 void myMethod(int y, double x) 会产生编译错误,因为 int 变量不能接受 double 值。
myMethod(3,(double)4); 应用于 void myMethod(double x,int y) 出于同样的原因会产生编译错误。

未找到唯一的最大特异性方法。所以编译器无法猜测应该调用哪个方法。出现编译错误。