多态简介
“多态”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长”。
通过合并各种特征与行为,封装技术可创建出新的数据类型。通过对具体实施细节的隐藏,可将接口与实施细节分离,使所有细节成为“private”(私有)。
扩展性
见下例:
//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;
class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
} ///:~
//: polymorphism/music/Wind.java
package polymorphism.music;
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
///:~
//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
}
/* Output:
Wind.play() MIDDLE_C
*///:~
以Instrument为例。因为存在多态,所以可以在不改变true()方法的前提下,根据需求向加入任意数量的新类型。在一个设计良好的OOP程序中,几乎所有方法都会遵从tune()的模型,只与基础类接口沟通。这样的程序具有“扩展性”,因为可以从通用的基础类继承新的数据类型,从而添加新功能。操作基础类接口的方法根本不需要更改就能适应新类的要求。
多态是一种至关重要的技术,允许程序员“将发生改变的东西同没有发生改变的东西区分开”(解耦合)
陷阱:“重写”private方法
子类(衍生类)不能重写父类(基本类(父类))的private方法,private方法是隐式的final。
陷阱:字段和static方法
注意:只有通常方法的调用可以使用多态;
如果直接方法字段,则访问将在编译时解析,见下例:
//: polymorphism/FieldAccess.java
// Direct field access is determined at compile time.
class Super {
public int field = 0;
public int getField() { return field; }
}
class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); }
} /* Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*///:~
可见sup.field和sub.field返回的值不同,对字段的调用不使用多态
static方法不适用多态
static方法只和类关联,和单独的对象没关系
构造方法和多态
理解构造方法是怎么在复杂层次结构和多态中工作的。
构造方法调用的顺序
保证所有的构造方法都得到调用,否则整个对象都不能被构造。
构造方法在复杂对象中调用的顺序为:
- 调用基本类(父类)的构造方法,即先调用最顶层基本类(父类)的构造方法,再依次调用衍生类的构造方法,该步骤重复多次直到最后衍生的类
- 按声明顺序初始化类成员
- 调用最后衍生类的构造方法
构造方法调用的顺序十分重要。进行继承操作时,必须知道所有的基本类(父类)且能访问这些基本类(父类)中的所有public和protected方法。即在衍生类中,要确保基本类(父类)中的所有类成员都是可用的。
在通常方法中,构造已经发生,所有对象的所有类成员都已构建。但在构造方法中,必须确保要使用的所有成员都已经构造。唯一能确保这一点的方法就是先调用基本类(父类)的构造方法,则在衍生类的构造方法中,所有要方法的基本类(父类)的成员都已经初始化。要注意的是所有成员类(通过合成)在类中定义的时候要进行初始化。
构造方法中多态方法的行为
如果在构造方法内部调用正在构造的对象的动态绑定方法会发生什么?
在普通方法中,动态绑定调用在运行期执行,因为对象不知道该方法是属于所在的类还是衍生的类
如果在构造方法中调用动态绑定方法,则要重写该方法。但是因为重写方法发生在对象被完全构造之前,所有会产生意想不到的效果,从而隐藏了一些难以找到的bug
调用当前构造方法时,其子类可能还没有初始化。然而动态绑定方法会“向外”进入层次结构,调用子类的方法。如果在构造方法内执行该操作,**可能调用的方法处理的是还没有初始化的类成员。**这样会带来灾难性的后果。
见下例:
//: polymorphism/PolyConstructors.java
// Constructors and polymorphism
// don’t produce what you might expect.
import static net.mindview.util.Print.*;
class Glyph {
void draw() {
print("Glyph.draw()");
}
Glyph() {
print("Glyph() before draw()");
draw(); //上溯造型时,先调用父类的构造方法,draw()调用的是子类重写的draw()方法,但是此时子类还没有构造,所以draw()方法里的radius还未初始化,输出0,
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r; //父类构造方法执行完,到子类构造方法,radius被初始化,输出5
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() { //重写了父类的draw()方法
print("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
/* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0 //为什么radius最开始是0,因为在所有操作发生之前,分配给对象的内存要先初始化为二进制0,所以radius先是0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
因而,尽量不要在构造方法中调用可以被重写的方法,要调就调用final或private方法