thinking in java笔记 8 多态
- 多态
多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。它不但能改善代码的组织结构和可读性,还能创建可扩展的程序。封装通过合并特征和行为来创建新的数据类型,‘实现隐藏’通过将细节私有化把接口和实现分离开来。多态的作用是消除类型之间的耦合关系。多态允许同一基类产生的不同子类之间根据方法的行为不同展现出差别(通过子类覆盖父类的方法实现),而这些方法的调用方式相同。
应用多态,我们所做的代码修改,不会对程序员中其他不应受到影响的部分产生破坏。它帮助我们将改变的事物和未变的事物分离开来。
- 再论向上转型
把对某个对象的引用视为对其基类型的引用的做法称为向上转型(在继承树的画法中,基类放置在上方)。
若各子类中存在对基类中同一个方法的重载,调用时可以调用其基类方法,然后进行运行时绑定,调用子类方法。
- 方法调用绑定
将一个方法调用和方法主体关联叫做绑定。
若在程序执行前进行绑定(编译器和连接程序实现),称为前期绑定(如C语言)。
在运行时根据对象的类型进行绑定,称为动态(运行时)绑定(java)。
java除了static方法和final方法(包含private方法)之外,都是动态绑定。
- 多态的缺陷
只有非private方法可以被覆盖。在导出类中,最好使用和基类中private方法不同的方法名。
域和静态方法不具备多态性。当sub对象转型为super引用时,任何域访问操作都将有编译器解析,因此不是多态的。
静态方法是与类,而非对象关联,因此也不具备多态性。
- 构造器与多态
基类的构造器总是在导出类的构造过程中被调用,构造器的任务是检查对象是否正确的被构造,而基类中的元素只有在基类的构造器中才能被初始化,只有按照顺序调用才能构建完整对象。
- 继承与清理
销毁的顺序应该和初始化顺序相反,字段应与声明的顺序相反。对于基类,应先清理导出类,再清理基类。
如果成员对象存在对象共享问题,不能直接清理,这时,须在被共享对象的类中对引用计数,当引用 数为0时再清理。
- 构造器内部的多态方法的行为
class Glyph {
void draw() { print ( "Glyph.draw()" ); }
Glyph () {
print (
draw();
print (
}
}
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
*///:~
Glyph ()构造函数中调用 draw()时,会调用 RoundGlyph 的 draw()方法。而此时 radius尚未初始化。
因此,编写构造器时有一个有效准则:只初始化值,尽量避免调用其他方法。
- 协变返回类型
在导出类的被覆盖方法中可以返回基类方法的返回类型的某种导出类型。
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(); }
}
- 用继承进行设计--状态模式
一条通用的准则是:用继承表现行为间的差异,并用字段表达状态上的变化。例子中通过继承得到两个不同的类,用语表达act()之间的差异;而Stage通过运用组合使自己的状态发生变化,在这种情况下,这种状态的改变也产生了行为的改变。
import static rasa.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
*///:~
- 用继承进行设计--纯继承与扩展
纯继承:导出类和基类拥有的方法相同(is-a关系)。导出类可以完全代替基类,使用时,不需要知道关于子类的任何额外信息。只需从导出类向上转型,永远不需要知道正在处理的对象的确切类型。
扩展:导出类像是一个基类,他有着相同的基本接口,但具有由额外方法实现的其他特性。导出类的接口的扩展部分不能被基类访问,因此需要重新查明对象的确切类型,以便能访问该类型的扩充方法。
向上转型会都是具体的类型信息,通过向下转型,能够获取类型信息,但必须保证向下转型的正确性。运行期间会对转型进行检查,以便保证它是我们希望的那种类型(运行时类型识别RTTI),若转型错误,会报ClassCastException。