Java继承与多态性
问题内容:
继承和多态都构成IS-
A关系吗?在运行时发生继承和“重写”多态性而在编译时发生“过载”多态性是真的吗?我之所以这样问,是因为许多论坛似乎给出了相互矛盾且常常令人困惑的答案。
谢谢!
问题答案:
对于问题的第一部分,我认为维基百科提供了一个很好的定义:
在面向对象的程序设计中,子类型多态或包含多态是类型理论中的一个概念,其中名称可以表示许多不同类的实例,只要它们与某个公共超类相关即可。包含多态性通常通过子类型化来支持,即,不同类型的对象可以完全替代另一种类型(其基本类型)的对象,因此可以通过公共接口进行处理。或者,可以通过类型强制(也称为类型转换)来实现包含多态性。
面向对象编程中的另一种称为多态性的 Wikipedia术语似乎很好地回答了您的问题。本文中的第二篇参考文章“
了解类型,数据抽象和多态性”也详细介绍了此问题。
Java中的这种子类型化功能是通过继承类和接口来实现的。尽管从继承的角度来看,Java的子类型功能可能并不总是很明显。以与泛型的协方差和逆方差为例。而且,数组在序列层次结构中的任何地方都不明显,但它们是可序列化和可克隆的。也可以说,通过原始扩展转换,Java中的数字类型也是多态的。并且运算符的行为取决于其操作数。
无论如何,继承在某些这种多态性的实现中起着重要作用。
重载与覆盖
问题的第二部分似乎是关于选择给定方法的实现。显然,如果一个类重写了一个方法,并且您创建了该类的实例,则即使要通过父类的引用访问该对象,也要调用该方法的重写版本。
正如您所指出的那样, 正确 选择方法的 实现
是在运行时完成的,现在,要调用的方法的签名是在编译时确定的。由于重载是关于具有相同名称和不同签名的不同方法的,所以这就是为什么重写方法选择发生在编译时的原因。
编译时的覆盖方法选择
第15.12节“
方法调用表达式
”中的Java语言规范(JLS)详细解释了编译器遵循的过程,以选择正确的方法进行调用。
在那里,您会注意到这是一个 编译时 任务。JLS在15.12.2小节中说:
此步骤使用 方法 的 名称 和 参数表达式的类型
来定位可访问和适用的方法。可能有不止一种这样的方法,在这种情况下,选择了最具体的方法。
要验证其编译时性质,可以执行以下测试。
声明一个这样的类并进行编译。
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
该类(带有主类的类),然后再次运行该程序,则输出将为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
该类。