5

我最近遇到了两个重载问题,我找不到答案,也没有 java 环境来运行一些测试代码。我希望有人可以通过汇编Java编译器遵循的所有规则列表来帮助我重载或交替将我指向一个已经存在的列表。

首先,当两个方法的区别仅在于最终的可变参数参数时,在什么情况下会调用每个方法,您可以在没有任何参数的情况下调用可变参数方法吗?

private void f(int a) { /* ... */ }
private void f(int a, int... b) { /* ... */ }

f(12); // calls the former? I would expect it to
f(12, (int[])null); // calls latter, but passes null for b? 
  // Can I force the compiler to call the second method in the same fashion
  // as would happen if the first method didn't exist?

第二个问题,当两种方法因从彼此继承的类型不同而被调用时?我希望调用最衍生的版本,并允许调用另一个版本。

interface A {}
class B implements A {}
class C implements A {}

private void f(A a) {}
private void f(B b) {}

f(new C()); // calls the first method
f(new B()); // calls the second method?
f((A)(new B()); // calls the first method using a B object?

这是两个示例,但作为代码阅读器,我更喜欢用于解决此问题的确切有序规则的规范列表,因为我经常没有时间设置构建环境来检查编译器在做什么。

4

2 回答 2

20

重载与覆盖

正如您所指出的,选择正确的方法实现是在运行时完成的,现在要调用的方法的签名是在编译时决定的。

在编译时重载方法选择

第 15.12 节方法调用表达式中的Java 语言规范(JLS)详细解释了编译器选择要调用的正确方法所遵循的过程。

在那里,您会注意到这是一个编译时任务。JLS 在第 15.12.2 小节中说:

此步骤使用方法的名称参数表达式的类型 来定位可访问和适用的方法。可能有多个这样的方法,在这种情况下选择最具体的一个。

通常,如果可变参数方法与其他候选方法竞争,它们是最后选择的,因为它们被认为不如接收相同参数类型的方法具体。

要验证它的编译时性质,您可以进行以下测试。

像这样声明一个类并编译它(即javac ChooseMethod.java)。

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

声明第二个类,它调用第一个类的方法并编译它(即javac MethodChooser.java)。

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

如果您运行程序(即java MethodChooser),输出会显示Number

现在,向该类添加第二个更具体的重载方法ChooseMethod,并重新编译它(但不要重新编译其他类)。

public void doSomething(Integer i) {
 System.out.println("Integer");
}

如果再次运行 main,输出仍然是Number.

基本上,因为它是在编译时决定的。如果您重新编译MethodChooser该类(带有 main 的类),并再次运行该程序,输出将是Integer.

因此,如果要强制选择重载方法之一,则参数的类型必须与编译时的参数类型相对应,而不仅仅是在运行时。

在运行时覆盖方法选择

同样,方法的签名是在编译时决定的,但实际的实现是在运行时决定的。

像这样声明一个类并编译它。

public class ChooseMethodA {
   public void doSomething(Number n){
    System.out.println("Number A");
   }
}

然后声明第二个扩展类并编译:

public class ChooseMethodB extends ChooseMethodA {  }

在 MethodChooser 类中,您可以:

public class MethodChooser {
    public static void main(String[] args) {
        ChooseMethodA m = new ChooseMethodB();
        m.doSomething(10);
    }
}

如果你运行它,你会得到输出Number A,这没关系,因为该方法没有被覆盖ChooseMethodB,因此被调用的实现是ChooseMethodA.

现在,添加一个覆盖的方法MethodChooserB

public void doSomething(Number n){
    System.out.println("Number B");
}

然后重新编译这个,然后再次运行 main 方法。

现在,你得到了输出Number B

因此,实现是在运行时选择的,不需要重新编译MethodChooser类。

于 2012-06-05T16:43:47.310 回答
1

First question:

Your assumption is correct. The second call to f() will call the varargs method. You can get the compiler to call the second method with:

private void f(int a) {
    f(a, null);
}

Second question:

Yes. However, you can't extend an interface. If you change A to an abstract class, things will compile.

于 2012-06-05T16:46:13.180 回答