在面相对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特性。
多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。
“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,叫做前期绑定。在运行时根据对象的类型进行绑定叫做后期绑定,也称动态绑定和运行时绑定。若一种语言要实现后期绑定,必须具备某种机制,以便在运行时能判断对象的类型。即编译器一致不知道对象的类型,但是方法调用机制能找到正确的方法并加以调用。
Java中除了static和final方法之外,其他所有的方法都是后期绑定。
public class PrivateOverride{
private void f(){ System.out.print("private f()"); }
public static void main(String args[]){
PrivateOverride po = new PrivateOverride();
po.f(); //output: private f()
}
}
class Derived extends PrivateOverride{
public vodi f(){ System.out.print("public f()"); }
}
由于private方法被自动认为是final方法,而且对导出类是屏蔽的。因此,Derived类中的f()方法是一个全新的方法。即只有非private方法才可以被覆盖。但仍需注意覆盖private方法的对象。因为编译器此时并不报错。
若某个方法是静态的,那么它的行为不具有多态。静态方式是与类而非单个对象相关的。
基类构造器总是在导出类的构造过程中被调用,而且按照继承层次,逐渐向上链接,以使每个基类的构造器都能得到调用。构造器有一项特殊的任务:检查对象是否被正确的构造。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。
调用构造器的顺序:调用基类构造器;按声明顺序调用成员的初始化方法;调用导出类构造器主体
通过组合和继承的方法来创建新类时,若遇到清理问题,则必须创建dispose()方法,并且由于继承的缘故,必须在导出类中覆盖dispose()方法。当覆盖被继承类的dispose()方法时,务必记住调用基类的dispose()方法,否则,基累的清理动作不会发生。
在继承关系中,若某些成员对象中存在与其他一个或多个对象共享的情况时,需要使用过引用计数器来跟踪仍旧访问着共享对象的对象常量。
class Shared{
private int refcount = 0;
private static long couter = 0;
private final long id = couter ++;
public Shared(){
System.out.print("Creating" + this);
}
public void addRef(){
refcount++;
}
protected void dispose(){
if(--refcount == 0)
System.out.print("Dispoing"+this);
}
public String toString(){
return "Shard"+this.id;
}
}
class Composing{
private Shared shared;
private static long couter = 0;
private final long id = couter ++;
public Composing(Shared shared){
System.out.print("Creating" + this);
this.shared = shared;
this.shared.addRef();
}
protected void dispose(){
System.out.print("Dispoing"+this);
shared.dispose();
}
public String toString(){
return "Composing"+this.id;
}
}
public class ReferenceCounting{
public static void main(String args[]){
Shared shared = new Shared();
Composing[] composing = {new Composing(shared), new Composing(shared), new Composing(shared), new Composing(shared)};
for(Composing c : composing)
c.dispose();
}
}
public class PolyContructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
class Glyph{
void draw(){
System.out.println("Glyph draw");
}
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius =5;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph(), radius=" + radius);
}
void draw(){
System.out.println("RoundGlyph(),radius=" + radius);
}
}
初始化构造器的实际过程是:在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0;调用基类构造器,此时调用被覆盖的draw方法;按照声明顺序调用成员的初始化方法;调用导出类构造器主体
编写构造器的时候有条有效的准则:用尽可能简单的方法使对象进入正常状态,若可以的话,避免调用其他方法。在构在器内唯一能够安全调用的那些方法是基类中的final方法。
Java SE中添加了协变返回类型。它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。
用继承表达行为间的差异,并用字段表达状态上的变化
纯粹的is-a关系:基类可以接受发送给导出类的任何信息。我们只需从导出类向上转型,通过多态来处理对象
is-like-a关系:导出类像一个基类,它有着相同的接口,但还具有额外方法实现其他特性
在Java中,所有转型都会得到检查。若类型不符,则会返回一个ClassCastException。这种运行时对类型进行检查的行为称作运行时类型识别RTTI