多态的作用是消除类型之间的耦合关系。
8.2 转机
除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定——它会自动发生。
根据设计来决定是否使用final,而不是处于视图提高性能的目的来使用final
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()
*///:~
private方法被自动认为是final方法,而且对导出类是屏蔽的,在此情况下,Derived类中的f()方法就是一个全新的方法。
如果某个方法是静态的,它的行为就不具有多态性。静态方法是类,而并非与单个的对象相关联。
8.3 构造器和多态
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。因为构造器具有一项特殊任务,检查对象是否被正确地构造。只有基类的构造器才具有恰当的只是和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。
调用构造器的顺序:
- 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,直到最底层的导出类。
- 按声明的顺序调用成员的初始化方法。
- 调用导出类构造器的主体。
通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理。当覆盖被继承类的清理方法时,记住调用基类版本的清理方法,否则,基类的清理动作就不会发生。
销毁的顺序应该和初始化顺序相反。
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
*///:~
- 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
- 如前所述那样调用基类构造器,此时调用被覆盖后的draw方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时发现radius的值为0;
- 按照声明的顺序调用成员的初始化方法。
- 调用导出类的构造器主体。
编写构造器时,用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法。
8.4 协変返回类型
表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。
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
*///:~
8.5 用继承进行设计
更好的方式是首先选择“组合”,尤其是不能十分确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层级结构中,而且更加灵活。
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
*///:~
在运行期间获得了动态灵活性,这也称为状态模式。一条通用准则是“用继承表达行为间的差异,并用字段表达状态上的变化”。通过继承得到了两个不同的类,用于表达act方法的差异,而Stage通过运用组合使自己的状态发生变化,这种状态的改变也就产生了行为的改变。
如果在导出类扩展了在基类中没有的其他方法,如果向上转型就会丢失具体的类型信息,向下转型可以获取类型信息。
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
}
} ///:~