多态通过做什么怎么做,从另一角度将接口的实现和分离开来。多态有助于代码的组织和扩展性。
封装,通过合并特征和行为来创建新的数据类型。
继承允许将对象视为它自己本身的类型或其基型来处理。
多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要他们都是从同一基类导出而来的。
8.1再论向上转型
向上转型:把对某个对象的引用视为对其基类型的引用的做法。
下面这段代码:Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。
//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~
//: 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
*///:~
当一个wind引用对象传递到tune()时,不需要任何类型转换,因为它的基类。
上边这样忘记对象类型的好处是,系统不需要为Instrument的每种类型都编写一个新的tune()方法。
//: polymorphism/music/Music2.java
// Overloading instead of upcasting.
package polymorphism.music;
import static net.mindview.util.Print.*;
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~
这样简单的方法,只接受基类作为参数,而不是那些特殊的导出类,这就是多态的特点。
8.2转机
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
编译器并不知道Instrument 引用指向的是Wind对象。
绑定:将一个方法调用同一个方法主体关联起来。
前期绑定:在程序执行前进行绑定。
后期绑定(动态绑定,运行时绑定):在运行的时候根据对象类型进行绑定。语言本身利用某种机制,在运行时判断对象的类型。
Java中除static和final方法(private方法属于final方法)之外,所有的方法都是后期绑定。
final可以防止别人覆盖方法,这样能有效的关闭动态绑定,不需要进行动态绑定。
Java中所有的方法都是通过动态绑定的,就可以编写只与基类打交道的关系了。
向上转型:Shape时Circle的基类。
Shape s = new Circle();
如果调用基类方法,已经在导出类中被覆盖:
s.draw();
其实调用的还时Circle的方法,因为后期绑定的原因。
//: polymorphism/shape/Shape.java
package polymorphism.shape;
public class Shape {
public void draw() {}
public void erase() {}
} ///:~
//: polymorphism/shape/Circle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Circle extends Shape {
public void draw() { print("Circle.draw()"); }
public void erase() { print("Circle.erase()"); }
} ///:~
//: polymorphism/shape/Square.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Square extends Shape {
public void draw() { print("Square.draw()"); }
public void erase() { print("Square.erase()"); }
} ///:~
//: polymorphism/shape/Triangle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Triangle extends Shape {
public void draw() { print("Triangle.draw()"); }
public void erase() { print("Triangle.erase()"); }
} ///:~
//: polymorphism/shape/RandomShapeGenerator.java
// A "factory" that randomly creates shapes.
package polymorphism.shape;
import java.util.*;
public class RandomShapeGenerator {
private Random rand = new Random(47);
public Shape next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
} ///:~
RandomShapeGenerator是一个工厂,可以为shape产生一个对象引用。
将改变的事务和未变的分离开来:
//: polymorphism/music3/Music3.java
// An extensible program.
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
class Instrument {
void play(Note n) { print("Instrument.play() " + n); }
String what() { return "Instrument"; }
void adjust() { print("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) { print("Wind.play() " + n); }
String what() { return "Wind"; }
void adjust() { print("Adjusting Wind"); }
}
class Percussion extends Instrument {
void play(Note n) { print("Percussion.play() " + n); }
String what() { return "Percussion"; }
void adjust() { print("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) { print("Stringed.play() " + n); }
String what() { return "Stringed"; }
void adjust() { print("Adjusting Stringed"); }
}
class Brass extends Wind {
void play(Note n) { print("Brass.play() " + n); }
void adjust() { print("Adjusting Brass"); }
}
class Woodwind extends Wind {
void play(Note n) { print("Woodwind.play() " + n); }
String what() { return "Woodwind"; }
}
public class Music3 {
// Doesn't care about type, so new types
// added to the system still work right:
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
覆盖“私有方法”:应该尽量设计方法名的时候避免这种情况的发生。
//: polymorphism/PrivateOverride.java
// Trying to override a private method.
package polymorphism;
import static net.mindview.util.Print.*;
public class PrivateOverride {
private void f() { print("private f()"); }
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() { print("public f()"); }
} /* Output:
private f()
*///:~
域与静态方法:
//: 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
*///:~
sub转型为super,任何访问操作都由编译器解释,不是多态。
如果某个方法是静态的,他的行为就不具备多态性:
//: polymorphism/StaticPolymorphism.java
// Static methods are not polymorphic.
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()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
} /* Output:
Base staticGet()
Derived dynamicGet()
*///:~
静态方法是与类,而并非与单个方法相关联的。
8.3构造器和多态
构造器实际上是static,实际上并不具备多态性。
导出类只能访问他自己的成员,而不能访问基类的成员(基类成员通常是private的),所以必须通过基类构造器对基类的元素进行初始化,所以导出类构造器中都要调用基类构造器(没有明确调用就是默认调用)。
组合、继承、多态在构建顺序上的作用:
//: polymorphism/Sandwich.java
// Order of constructor calls.
package polymorphism;
import static net.mindview.util.Print.*;
class Meal {
Meal() { print("Meal()"); }
}
class Bread {
Bread() { print("Bread()"); }
}
class Cheese {
Cheese() { print("Cheese()"); }
}
class Lettuce {
Lettuce() { print("Lettuce()"); }
}
class Lunch extends Meal {
Lunch() { print("Lunch()"); }
}
class PortableLunch extends Lunch {
PortableLunch() { print("PortableLunch()");}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() { print("Sandwich()"); }
public static void main(String[] args) {
new Sandwich();
}
} /* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~
顺序:
调用基类构造器,按声明顺序调用成员初始化方法,调用导出类构造器的主体。
继承与清理:
对象销毁顺序应该和初始化相反,对于字段和声明顺序相反,共享对象情况比较复杂,需要引用计数来解决。
构造器内部的多态方法的行为:
在一般的方法内部,动态绑定的调用是在运行时才决定的,因为对象无法知道它属于方法所在的那个类,还是属于那个类的导出类。
构造器的工作时创建对象。
//: 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();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void 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
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
上边代码执行顺序:初始化的实际过程
在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;
调用基类构造器,此时调用被覆盖后的draw方法,要在调用RoundGlyph构造器之前调用,由于第一步,所以radius值为0;
按照声明的顺序调用成员的初始化方法;
调用导出类的构造器主体;
在构造器中唯一能够安全调用的方法时基类中的final方法。包括private方法。这些方法不能被覆盖,就不会出现draw方法的情况。
8.4协变返回类型
协变返回类型:在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。
//: polymorphism/CovariantReturn.java
class Grain {
public String toString() { return "Grain"; }
}
class Wheat extends Grain {
public String toString() { return "Wheat"; }
}
class Mill {
Grain process() { return new Grain(); }
}
class WheatMill extends Mill {
Wheat process() { return new Wheat(); }
}
public class CovariantReturn {
public static void main(String[] args) {
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
} /* Output:
Grain
Wheat
*///:~
g = m.process();并不会报错。
8.5用继承进行设计
继承是有层次结构的,继承在编译时就需要知道确切类型。
//: polymorphism/Transmogrify.java
// Dynamically changing the behavior of an object
// via composition (the "State" design pattern).
import static net.mindview.util.Print.*;
class Actor {
public void act() {}
}
class HappyActor extends Actor {
public void act() { print("HappyActor"); }
}
class SadActor extends Actor {
public void act() { print("SadActor"); }
}
class Stage {
private Actor actor = new HappyActor();
public void change() { actor = new SadActor(); }
public void performPlay() { actor.act(); }
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
} /* Output:
HappyActor
SadActor
*///:~
用继承表达行为间的差异,并用字段表示状态上的变化。
纯继承与扩展:只有在基类中已经建立的方法才可以在导出类中被覆盖。
向下转型与运行时类型识别:
//: polymorphism/RTTI.java
// Downcasting & Runtime type information (RTTI).
// {ThrowsException}
class Useful {
public void f() {}
public void g() {}
}
class MoreUseful extends Useful {
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
} ///:~
8.6总结
应用多态,是自己编程的视野更广。使自己的程序不仅包含个别类的成员和消息,而且还要包括类与类之间的共同特性以及他们之间的关系。