Java重载规则

问题描述 投票:0回答:2

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

首先,当两个方法仅因最终 varargs 参数不同时,在什么情况下调用每个方法?您可以在没有任何参数的情况下调用 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?

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

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?

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

java overloading
2个回答
22
投票

重载与覆盖

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

编译时重载方法选择

第 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
 类添加第二个更具体的 
overloaded

方法,然后重新编译它(但不要重新编译其他类)。

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

 类。

第一个问题:

你的假设是正确的。第二次调用

1
投票
将调用 varargs 方法。您可以让编译器调用第二个方法:

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

第二个问题:

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

© www.soinside.com 2019 - 2024. All rights reserved.