我确实知道覆盖和重载之间的语法差异。而且我也知道overriding是运行时多态,而重载是编译时多态。但我的问题是:“重载是真的编译时多态吗?方法调用是否真的在编译时解决?”。为了澄清我的观点,让我们考虑一个示例类。
public class Greeter {
public void greetMe() {
System.out.println("Hello");
}
public void greetMe(String name) {
System.out.println("Hello " + name);
}
public void wishLuck() {
System.out.println("Good Luck");
}
}
由于所有方法greetMe(), greetMe(String name), wishLuck()
都是公开的,所以它们都可以被覆盖(包括超载的方法),对吧?例如,
public class FancyGreeter extends Greeter {
public void greetMe() {
System.out.println("***********");
System.out.println("* Hello *");
System.out.println("***********");
}
}
现在,请考虑以下代码段:
Greeter greeter = GreeterFactory.getRandomGreeter();
greeter.greetMe();
getRandomGreeter()
方法返回一个随机Greeter
对象。它可以返回Greeter
或其任何子类的对象,如FancyGreeter
或GraphicalGreeter
或任何其他子类。 getRandomGreeter()
将使用new
创建对象,或者动态加载类文件并使用反射创建对象(我认为可以使用反射)或任何其他可能的方式。 Greeter
的所有这些方法可能会或可能不会在子类中被覆盖。因此编译器无法知道是否覆盖了特定方法(是否重载)。对?此外,维基百科在Virtual functions上说:
在Java中,默认情况下所有非静态方法都是“虚函数”。 只有标有关键字final的方法才能覆盖, 与非继承的私有方法一起,是非虚拟的。
因为,使用动态方法分派在运行时解析虚函数,并且由于所有非私有,非最终方法都是虚拟的(无论是否过载),因此必须在运行时解析它们。正确?
然后,如何在编译时解决重载?或者,有什么我误解了,或者我错过了什么?
答案 0 :(得分:14)
每个'Greeter'类都有3种虚拟方法:void greetMe()
,void greetMe(String)
和void wishLuck()
。
当你调用greeter.greetMe()
时,编译器可以确定应该从方法签名中调用三个虚拟方法中的哪一个 - 即。 void greetMe()
因为它不接受任何参数。调用void greetMe()
方法的具体实现取决于greeter
实例的类型,并在运行时解析。
在您的示例中,编译器可以计算出要调用的方法,因为方法签名完全不同。显示“编译时多态”概念的一个稍好的示例可能如下:
class Greeter {
public void greetMe(Object obj) {
System.out.println("Hello Object!");
}
public void greetMe(String str) {
System.out.println("Hello String!");
}
}
使用此greeter类将得到以下结果:
Object obj = new Object();
String str = "blah";
Object strAsObj = str;
greeter.greetMe(obj); // prints "Hello Object!"
greeter.greetMe(str); // prints "Hello String!"
greeter.greetMe(strAsObj); // prints "Hello Object!"
编译器将使用编译时类型选择具有最特定匹配的方法,这就是第二个示例工作并调用void greetMe(String)
方法的原因。
最后一个调用是最有趣的:尽管strAsObj的运行时类型是String
,但它已被强制转换为Object
,这就是编译器看到它的方式。因此,编译器可以为该调用找到的最接近的匹配是void greetMe(Object)
方法。
答案 1 :(得分:12)
如果你问的话,仍然可以覆盖重载的方法。
重载方法就像不同的系列一样,即使它们具有相同的名称。编译器在给定签名的情况下静态选择一个系列,然后在运行时将其分派给类层次结构中最具体的方法。
即,方法调度分两步执行:
call
,该签名在声明的对象类型的重载方法列表中最符合当前方法参数该方法被调用。如果方法参数类型根本不是协变的,那么重载相当于在编译时损坏方法名称;因为它们实际上是不同的方法,JVM永远不会根据接收器的类型交替地发送它们。
答案 2 :(得分:10)
什么是多态?
度Acc。对我来说:如果一个实体可以用多种形式表示,那么该实体就会表现出多态性。
现在,让我们将这个定义应用于Java构造:
1)运算符重载是编译时多态性。
例如,+
运算符可用于添加两个数字OR以连接两个字符串。它是多态性的一个例子,严格地说是编译时多态性。
2)方法重载是编译时多态性。
例如,具有相同名称的方法可以具有多个实现。它也是一个编译时多态性。
It's compile-time because before execution of program compiler decides the flow of program i.e which form will be used during run-time.
3)方法覆盖是运行时多态性。
例如,具有相同签名的方法可以具有多个实现。这是一个运行时多态性。
4)基类使用代替派生类是运行时多态。
例如,interface
引用可以指向它的任何实现者。
It's run-time because the flow of program can't be known before execution i.e. only during run-time it can be decided that which form will be used.
我希望它有点清楚。
答案 3 :(得分:3)
在这方面的重载意味着函数的类型在编译时是静态确定的,而不是动态调度。
幕后真正发生的是,对于名为“foo”且类型为“A”和“B”的方法,会创建两个方法(“foo_A”和“foo_B”)。要在调用时确定调用哪一个(foo((A) object)
或foo((B) object)
导致调用foo_A
或foo_B
)。所以在某种程度上这个是编译时多态,尽管真正的方法(即要在类层次结构中采用哪种实现)是在运行时确定的。
答案 4 :(得分:0)
我强烈反对将方法重载称为编译时多态性。
我同意方法重载是静态绑定(编译时),但是我没有看到其中的多态性。
我试图在我的问题中表达自己的观点以得到澄清。您可以参考this link.