书中源代码:https://github.com/yangxian1229/ThinkingInJava_SourceCode
多态(也称作动态绑定,后期绑定或运行时绑定)。
8.1 再论向上转型
继承允许将对象视为它自己的类型或其基类类型来加以处理。
8.2 转机
8.2.1 方法调用绑定
将一个方法用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,叫做前期绑定。
后期绑定的含义是在运行时根据对象的类型进行绑定。
Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
8.3 构造器和多态
尽管构造器并不具有多态性(它们实际是static方法),但还是非常有必要理解构造器怎么样通过多态在复杂的层次结构中运作。
8.3.1 构造器的调用顺序
复杂对象调用构造器要遵照下面的顺序:
1)调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类。
2)按声明顺序调用成员的初始化方法。
3)调用导出类构造器的主体。
8.3.2 继承与清理
如果某个子对象要依赖于其他对象,销毁的顺序应该和初始化顺序相反。对于字段,则意味着与声明顺序相反。如果一些成员存在于其他一个或多个对象共享的情况,问题就变得复杂了,这种情况下,也许就必需使用引用计数来跟踪仍旧访问着共享对象的对象数量了。
package ch8;
import static net.mindview.util.Print.*;
class Shared{
private int refcount = 0;
private static long counter = 0;
private final long id = counter++;
public Shared(){
print("Creating "+id);
}
public void addRef(){refcount++;}
protected void dispose(){
if(--refcount == 0)
print("Disposing"+this);
}
public void finalize(){
if(refcount != 0)
print("Error: object is not properly cleaned-up!");
}
public String toString(){return "Shared "+id;}
}
class Composing{
private Shared shared;
private static long counter = 0;
private final long id = counter++;
public Composing(Shared shared){
print("Creating "+this);
this.shared = shared;
this.shared.addRef();
}
protected void dispose(){
print("disposing "+this);
shared.dispose();
}
public String toString(){return "Composing "+id;}
}
public class ReferenceCount {
public static void main(String[] args) {
Shared shared = new Shared();
Composing[] composing = {new Composing(shared),
new Composing(shared),new Composing(shared),
new Composing(shared),new Composing(shared)};
for(Composing c:composing)
c.dispose();
}
}/* Output:
Creating 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
DisposingShared 0
*///;~
static long counter跟踪所创建的Shared的实例的数量,还可以为id提供数值。counter的类型是long而不是int,这样可以防止溢出。id是final的,因为我们不希望它的值在对象生命周期中被改变。
在将一个共享对象附着到类上时,必须记住调用addRef(),但是dispose()方法将跟踪引用数,并决定何时执行清理。
8.3.3 构造器内部的多态方法的行为
如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,那会发生什么情况呢?
package ch8;
import static net.mindview.util.Print.*;
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
*///;~
初始化的实际过程是:
1)在其他任何事物发生之前,将分配给对象的存储空间 初始化成二进制的零。
2)如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0。
3)按照声明的顺序调用成员的初始化方法。
4)调用导出类的构造器主体。
编写构造器有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。”
8.4 协变返回类型
它表示在导出类中被覆盖方法可以返回基类方法的返回类型的某种导出类型。(没太看懂这句话。。。)
package ch8;
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
*///:~
协变返回类型允许返回更具体的Wheat类型。
8.5 用继承进行设计
状态模式(练习16)
8.5.1 纯继承与扩展
8.5.2 向下转型与运行时类型识别
在Java语言中,所有转型都会得到检查!所以即使我们只是进行了一次普通的加括弧形式的类型转换,在进入运行时期仍然会对其进行检查,以便保证它的确是我们希望的那种类型。如果不是,就会返回一个ClassCastException(类型转换异常)。这种在运行期间对类型进行检查的行为称作“运行时类型识别”(RTTI)。
(练习17)