// polymorphism/music/Note.java
// Notes to play on musical instruments
package polymorphism.music;
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
}
枚举已经在”第 6 章初始化和清理“一章中介绍过了。
这里,Wind 是一种 Instrument;因此,Wind 继承 Instrument:
// polymorphism/music/Instrument.java
package polymorphism.music;
class Instrument {
public void play(Note n) {
System.out.println("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:
@Override
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
Music 的方法 tune() 接受一个 Instrument 引用,同时也接受任何派生自 Instrument 的类引用:
// polymorphism/music/Music.java
// Inheritance & upcasting
// {java polymorphism.music.Music}
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
}
}
Java 中除了 static 和 final 方法(private 方法也是隐式的 final)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。
为什么将一个对象指明为 final ?正如前一章所述,它可以防止方法被重写。但更重要的一点可能是,它有效地”关闭了“动态绑定,或者说告诉编译器不需要对其进行动态绑定。这可以让编译器为 final 方法生成更高效的代码。然而,大部分情况下这样做不会对程序的整体性能带来什么改变,因此最好是为了设计使用 final,而不是为了提升性能而使用。
// polymorphism/shape/RandomShapes.java
// A "factory" that randomly creates shapes
package polymorphism.shape;
import java.util.*;
public class RandomShapes {
private Random rand = new Random(47);
public Shape get() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
public Shape[] array(int sz) {
Shape[] shapes = new Shape[sz];
// Fill up the array with shapes:
for (int i = 0; i < shapes.length; i++) {
shapes[i] = get();
}
return shapes;
}
}
array() 方法分配并填充了 Shape 数组,这里使用了 for-in 表达式:
// polymorphism/Shapes.java
// Polymorphism in Java
import polymorphism.shape.*;
public class Shapes {
public static void main(String[] args) {
RandomShapes gen = new RandomShapes();
// Make polymorphic method calls:
for (Shape shape: gen.array(9)) {
shape.draw();
}
}
}
// 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;
@Override
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())
}
}
当 Sub 对象向上转型为 Super 引用时,任何属性访问都被编译器解析,因此不是多态的。在这个例子中,Super.field 和 Sub.field 被分配了不同的存储空间,因此,Sub 实际上包含了两个称为 field 的属性:它自己的和来自 Super 的。然而,在引用 Sub 的 field 时,默认的 field 属性并不是 Super 版本的 field 属性。为了获取 Super 的 field 属性,需要显式地指明 super.field。
// polymorphism/Frog.java
// Cleanup and inheritance
// {java polymorphism.Frog}
package polymorphism;
class Characteristic {
private String s;
Characteristic(String s) {
this.s = s;
System.out.println("Creating Characteristic " + s);
}
protected void dispose() {
System.out.println("disposing Characteristic " + s);
}
}
class Description {
private String s;
Description(String s) {
this.s = s;
System.out.println("Creating Description " + s);
}
protected void dispose() {
System.out.println("disposing Description " + s);
}
}
class LivingCreature {
private Characteristic p = new Characteristic("is alive");
private Description t = new Description("Basic Living Creature");
LivingCreature() {
System.out.println("LivingCreature()");
}
protected void dispose() {
System.out.println("LivingCreature dispose");
t.dispose();
p.dispose();
}
}
class Animal extends LivingCreature {
private Characteristic p = new Characteristic("has heart");
private Description t = new Description("Animal not Vegetable");
Animal() {
System.out.println("Animal()");
}
@Override
protected void dispose() {
System.out.println("Animal dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
class Amphibian extends Animal {
private Characteristic p = new Characteristic("can live in water");
private Description t = new Description("Both water and land");
Amphibian() {
System.out.println("Amphibian()");
}
@Override
protected void dispose() {
System.out.println("Amphibian dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
public class Frog extends Amphibian {
private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");
public Frog() {
System.out.println("Frog()");
}
@Override
protected void dispose() {
System.out.println("Frog dispose");
t.dispose();
p.dispose();
super.dispose();
}
public static void main(String[] args) {
Frog frog = new Frog();
System.out.println("Bye!");
frog.dispose();
}
}
输出:
Creating Characteristic is alive
Creating Description Basic Living Creature
LivingCreature()
Creating Characteristiv has heart
Creating Description Animal not Vegetable
Animal()
Creating Characteristic can live in water
Creating Description Both water and land
Amphibian()
Creating Characteristic Croaks
Creating Description Eats Bugs
Frog()
Bye!
Frog dispose
disposing Description Eats Bugs
disposing Characteristic Croaks
Amphibian dispose
disposing Description Both wanter and land
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Creature
disposing Characteristic is alive
层级结构中的每个类都有 Characteristic 和 Description 两个类型的成员对象,它们必须得被销毁。销毁的顺序应该与初始化的顺序相反,以防一个对象依赖另一个对象。对于属性来说,就意味着与声明的顺序相反(因为属性是按照声明顺序初始化的)。对于基类(遵循 C++ 析构函数的形式),首先进行派生类的清理工作,然后才是基类的清理。这是因为派生类的清理可能调用基类的一些方法,所以基类组件这时得存活,不能过早地被销毁。输出显示了,Frog 对象的所有部分都是按照创建的逆序销毁的。
因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 final 方法(这也适用于可被看作是 final 的 private 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。
协变返回类型
Java 5 中引入了协变返回类型,这表示派生类的被重写方法可以返回基类方法返回类型的派生类型:
// polymorphism/CovariantReturn.java
class Grain {
@Override
public String toString() {
return "Grain";
}
}
class Wheat extends Grain {
@Override
public String toString() {
return "Wheat";
}
}
class Mill {
Grain process() {
return new Grain();
}
}
class WheatMill extends Mill {
@Override
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);
}
}
// polymorphism/Transmogrify.java
// Dynamically changing the behavior of an object
// via composition (the "State" design pattern)
class Actor {
public void act() {}
}
class HappyActor extends Actor {
@Override
public void act() {
System.out.println("HappyActor");
}
}
class SadActor extends Actor {
@Override
public void act() {
System.out.println("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();
}
}
// polymorphism/RTTI.java
// Downcasting & Runtime type information (RTTI)
// {ThrowsException}
class Useful {
public void f() {}
public void g() {}
}
class MoreUseful extends Useful {
@Override
public void f() {}
@Override
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
}
}
输出:
Exception in thread "main"
java.lang.ClassCastException: Useful cannot be cast to
MoreUseful
at RTTI.main