Java重载规则

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

首先,当两个方法只有最终的varargs参数不同时,在什么情况下每个方法都会被调用,你可以在没有任何args的情况下调用varargs方法吗?

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? 

第二个问题,当两个方法因为相互inheritance而被调用的类型不同时? 我希望调用最多派生的版本,并且允许调用另一个版本。

 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? 

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

重载与覆盖

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

编译时的重载方法选择

第15.12节“ 方法调用表达式 ”中的Java语言规范 (JLS)详细说明了编译器在选择正确的调用方法时遵循的过程。

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

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

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

要validation此编译时的性质,您可以执行以下测试。

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

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

声明第二个类,它调用第一个类的方法并编译它。

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

如果调用main,则输出显示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被覆盖,因此被调用的实现是ChooseMethodB的实现。

现在,在MethodChooserB添加一个overriden方法:

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

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

现在,您获得输出Number B

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

第一个问题:

你的假设是正确的。 第二次调用f()将调用varargs方法。 您可以让编译器调用第二个方法:

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

第二个问题:

是。 但是,您无法扩展接口。 如果将A更改为抽象类,则会编译。