在面向对象的程序设计语言中,多态是继数据和继承之后的第三张基本特征
多态不但能够改善代码组织结构和可读性,还能够创建可扩展的程序——即无论在项目最初创建时还是在需要添加新功能时都可以"生长"的程序。
封装通过合并特征和行为来创建新的数据类型。"实现隐藏"则通过将细节"私有化",把接口和实现分离开来。多态的作用就是消除类型之间的耦合关系。
多态方法调用允许一种类型表现出与其他相似类型之间的区别。
8.1 再论向上转型
把某个对象的引用视作对其基类的引用称为向上转型——因为再继承树中,基类是放置在上方的。
8.2 转机
基类如何知道引用指向哪个导出类?
8.2.1 方法调用绑定
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,由编译器和连接程序实现,叫做前期绑定,此为面向过程语言默认绑定方式。
后期绑定:就是在运行时,根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。
Java中除了satic方法和final方法之外,其他所有都是后期绑定。
8.2.3 可扩展性
在一个设计良好的OOP程序中,程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而新添一些功能。那些操作基类接口的方法不需要任何改动就可以应用于新类。
8.2.4 缺陷:覆盖私有方法
public class PrivateOverride {
private void f(){
System.out.println("private f()");
}
public static void main(String[] args){
PrivateOverride po=new Derived();
po.f();
}
}
class Derived extends PrivateOverride{
public void f(){
System.out.println("public f()");
}
}
只有非private方法才可以被覆盖,在导出类中,对于基类的private方法,最好采用不同的名字。
8.2.5 缺陷:域与静态方法
public class FieldAccess {
public static void main(String[] args){
Super sup=new Sub();
System.out.println("sup.feld="+sup.field+",sup.getField()="+sup.getField()+"");
//output:sup.feld=0,sup.getField()=1
Sub sub=new Sub();
System.out.println("sub.field="+sub
.field+",sub.getField()="+sub.getField()+",getSuperField="+sub.getSuperField()+"");
//output:sub.field=1,sub.getField()=1,getSuperField=0
}
}
class Super{
public int field=0;
public int getField(){
return field;
}
}
//Sub包含了两个域,自己的field和继承来的field
class Sub extends Super{
public int field=1;
public int getField(){
return field;
}
public int getSuperField(){
return super.field;
}
}
如果某个方法是静态的,它的行为就不具有多态性
public class StaticPolymorphism {
public static void main(String[] args){
StaticSuper sup=new Staticsub();
System.out.println(sup.staticGet());//output:Base staticGet()
System.out.println(sup.dynamicGet());//output:Derived dynamicGet()
}
}
class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}
public String dynamicGet(){
return "Base dynamicGet()";
}
}
class Staticsub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
静态方法是与类,而非与单个的对象相关联
8.3 构造器和多态
构造器并不具有多态性,它们实际上是static方法,只不过该static声明是隐试的。
8.3.1 构造器的调用顺序
基类构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。意义:构造器具有一项特殊的任务,检查对象是否被正确的构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常使private类型)。只有基类的构造器才具有恰到的知识权限来对自己的元素进行初始化。
对象调用构造器遵循以下顺序:
- 1.调用基类构造器
- 2.按声明顺序调用成员初始化方法
- 3.调用导出类构造器的主体
8.3.2 继承与清理
通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理。
8.3.3 构造器内部的多态方法的行为
如果调用构造器内部的一个动态绑定方法,就要用到哪个方法的覆盖后的定义。然而,这个调用效果可能难以预测,因为被覆盖的方法在对象完全构造之前就会被调用。
public class PolyConstructors {
public static void main(String[] args){
new RoundGlyph(5);
}
}
class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
Glyph(){//2
System.out.println("Glyph() before draw()");//3
draw();//4
System.out.println("Glyph() after draw()");//6
}
}
class RoundGlyph extends Glyph{
private int radius=1;//7
RoundGlyph(int r){//1
radius=r;//8
System.out.println("RoundGlyph.RoundGlyph().radius="+radius+"");//9
}
void draw(){
System.out.println("RoundGlyph.draw(),radius="+radius+"");//5
}
}
初始化过程:
- 1.在其他任何事物发送之前,将分配对象的存储控件初始化成二进制零
- 2.调用基类构造器
- 3.按照声明顺序调用成员初始化方法
- 4.调用导出类的构造器主体