在面向对象的程序设计中,多态是继数据抽象和继承之后的第三种基本特征。
1、再论向上转型
/**
* 基类
*/
public class Instrument {
public void play(Note n) {
System.out.println("Instrument play()");
}
}
/**
* 导出类
*/
public class Wind extends Instrument{
public void play(Note n) {
System.out.println("Wind play()"+n);
}
}
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);
}
}
向上转型屏蔽了对象的类型,避免了为每个对象都重载一次tune方法。
2、转机
2.1 方法调用绑定
将一个方法盗用同一个方法主体关联起来被称为绑定。若在程序执行前进行绑定,叫前期绑定,在运行时个根据对象的类型进行绑定,叫后期绑定,也叫动态绑定或运行时绑定。
Java中除了static方法和final方法,其他方法都是后期绑定。
2.2 产生正确行为
通过动态绑定实现多态,在编译时,编译器不需要获得任何特殊信息就能进行正确的调用。
2.3 可拓展性
在一个良好的oop程序中,都可以从通用的基类中继承出新的数据类型,从而新添加一些功能,那些操作基类的接口方法不需要任何改动就可以应用于新类。
2.4 缺陷:覆盖私有方法
只有非private方法才可以被覆盖,但是还要密切注意覆盖private方法现象这时,编译器不会报错,但也不会按照我们所期望的来执行,确切的说,在导出类中,对于基类中的private方法最好采用不同的名字,还是第四章中总结的三句话;
2.5 缺陷:域与静态方法
只有普通方法调用可以是多态的,静态方法与类相关联,而非单个对象。
3、构造器和多态
构造器实际上是static方法,只不过static的声明是隐式的;
3.1 构造器的调用顺序
- 调用基类构造器;
- 按声明顺序调用成员的初始化方法;
- 调用到处类构造器的主体;
3.2 继承与清理
销毁的顺序和初始化顺序相反,应先对导出类清理然后是基类;
如果某些成员对象中存在于其他一个或多个对象共享的情况,就不能简单地假设可以调用dispose了,也许就必须使用引用计数来跟踪仍旧访问着共享对象的对象数量了;
3.3 构造器内部多态方法的行为
构造器的工作实际上的创建对象,如果在一个构造器的内部调用正在构造对象的某个动态绑定方法会发生什么情况?
如果要调用构造器内部的一个动态绑定方法,子类就要对这个方法进行覆盖。
/**
* 构造器内部动态调用
*/
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
public Glyph() {
System.out.println("Glyph before draw()");
draw();
System.out.println("Glyph after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
public RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyph(),radius = "+radius);
}
void draw(){
System.out.println("RoundGlyph.draw,radius = "+radius);
}
}
输出结果:
Glyph before draw()
RoundGlyph.draw,radius = 0 //实现了动态调用,但radius 初始值并不为1
Glyph after draw()
RoundGlyph.RoundGlyph(),radius = 5
构造器的初始化实际过程:
- 在其他任何事发生之前,将分配给对象的存储空间初始化成二进制零;
- 调用基类构造器(调用被覆盖的draw方法),由于1的原因,radius 的值为0;
- 按照声明的顺序调用成员的初始化方法 ;
- 调用导出类的构造器主体;
所以在构造器中唯一能安全调用是是final修饰的方法,应当避免调用动态方法。
4、协变返回类型
表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型;
/**
* 协变返回类型
* @author Administrator
*
*/
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);
}
}
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();
}
}
5 用继承继续设计
相比继承,组合模式则更加灵活,可以动态选择类型,相反继承在编译时就需要知道确切类型,可以在运行时改变对象状态。
/**
* 状态模式
* @author Administrator
*
*/
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
class Actor{
public void act() {}
}
class HappyActor extends Actor{
public void act() {
System.out.println("HappyActor");
}
}
class SadActor extends Actor{
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();
}
}
向下转型过程中需要进行类型转换,如果不存在则会抛出异常,这种在运行期间对类型检查的行为称作运行时类型识别(RTTI),他不仅包括转型处理,还提供一种方法,可以在向下专心之前查看所要处理的类型。