Ock*_*zor 5 java polymorphism inheritance
继承和多态是否构成IS-A关系?并且在运行时发生继承和"重写"多态是真的,而在编译时发生"重载"多态吗?我之所以这么说是因为很多论坛似乎都给出了相互矛盾且经常令人困惑的答案.
谢谢!
对于问题的第一部分,我认为维基百科提供了一个很好的定义:
在面向对象的编程中,子类型多态性或包含多态性是类型理论中的概念,其中名称可以表示许多不同类的实例,只要它们与某些共同的超类相关.通常通过子类型支持包含多态性,即,不同类型的对象完全可替代另一种类型的对象(它们的基本类型),因此可以通过公共接口来处理.或者,包合多态性可以通过类型强制来实现,也称为类型转换.
在面向对象编程中,另一个名为Polymorphism的 Wikipedia artile 似乎很好地回答了你的问题.本文中的第二篇参考文章称为理解类型,数据抽象和多态性,也非常详细地介绍了这一问题.
除了其他方法之外,Java中的这种子类型功能通过继承类和接口来实现.尽管Java的子类型特征在继承方面可能并不明显.例如,协方差和与泛型相反的情况.此外,数组是Serializable和Cloneable,尽管在类型层次结构中的任何位置都不明显.也可以说通过原始扩展转换,Java中的数字类型也是多态的.并且操作符根据其操作数进行polimorphically行为.
无论如何,继承在实现某些多态性中起着重要作用.
重载与覆盖
问题的第二部分似乎是关于选择给定方法的实现.显然,如果类重写了一个方法并且您创建了该类的实例,则希望调用该方法的重写版本,即使您通过父类的引用访问该对象也是如此.
正如您所指出的那样,选择正确的方法实现是在运行时完成的,现在要调用的方法的签名是在编译时决定的.由于重载是关于具有相同名称和不同签名的不同方法,因此可以说在编译时发生重写方法选择.
在编译时重写方法选择
第15.12节" 方法调用表达式 "中的Java语言规范(JLS)详细说明了编译器在选择正确的调用方法时遵循的过程.
在那里,您会注意到这是一个编译时任务.JLS在第15.12.2小节中说:
此步骤使用方法的名称和参数表达式的类型 来定位可访问和适用的方法可能有多个此类方法,在这种情况下,选择最具体的方法.
要验证此编译时的性质,您可以执行以下测试.
声明一个这样的类并编译它.
public class ChooseMethod {
public void doSomething(Number n){
System.out.println("Number");
}
}
Run Code Online (Sandbox Code Playgroud)
声明第二个类,它调用第一个方法并编译它.
public class MethodChooser {
public static void main(String[] args) {
ChooseMethod m = new ChooseMethod();
m.doSomething(10);
}
}
Run Code Online (Sandbox Code Playgroud)
如果你调用main,输出说Number.
现在,向类添加第二个更具体的重载方法ChooseMethod,并重新编译它(但不要重新编译其他类).
public void doSomething(Integer i) {
System.out.println("Integer");
}
Run Code Online (Sandbox Code Playgroud)
如果再次运行main,则输出仍然是Number.
基本上,因为它是在编译时决定的.如果您重新编译MethodChooser该类(具有main的类),并再次运行该程序,则输出将是Integer.
因此,如果要强制选择其中一个重载方法,则参数类型必须与编译时参数的类型相对应,而不仅仅是在运行时.
在运行时覆盖方法选择
同样,方法的签名在编译时决定,但实际的实现是在运行时决定的.
声明一个这样的类并编译它.
public class ChooseMethodA {
public void doSomething(Number n){
System.out.println("Number A");
}
}
Run Code Online (Sandbox Code Playgroud)
然后声明第二个扩展类并编译:
public class ChooseMethodB extends ChooseMethodA { }
Run Code Online (Sandbox Code Playgroud)
在MethodChooser类中,您可以:
public class MethodChooser {
public static void main(String[] args) {
ChooseMethodA m = new ChooseMethodB();
m.doSomething(10);
}
}
Run Code Online (Sandbox Code Playgroud)
如果你运行它,你得到输出Number A,这是好的,因为该方法尚未被覆盖ChooseMethodB,因此被调用的实现是ChooseMethodA.
现在,添加一个覆盖方法MethodChooserB:
public void doSomething(Number n){
System.out.println("Number B");
}
Run Code Online (Sandbox Code Playgroud)
并重新编译这一个,并再次运行main方法.
现在,你得到了输出 Number B
因此,在运行时选择了实现,而不需要重新编译MethodChooser类.