再论向上转型
由于继承的存在, 我们可以将子类的对象引用当做基类的一个对象, 即将子类对象向上转型为基类对象.
class A {
public void show() {
System.out.println("A show()");
}
}
class B extends A{
public void show() {
System.out.println("B show()");
}
}
public class C {
public static void func(A a) {
a.show();
}
public static void main(String[] args) {
B b = new B();
// B show()
func(b);
}
}
动态绑定
绑定
将一个方法调用同一个方法主体关联起来被称作绑定, 在程序执行前进行绑定称为前期绑定.
当我们编写上例中的代码:
public static void func(Cycle c) {
c.ride();
}
我们如何确定方法ride所对应的对象c是什么类型?
这就引发了动态绑定的概念. 即在静态编译阶段, 我们实际上并不知道c的类型是什么. 只有等到运行阶段, 我们通过某种方法, 获得此方法的类签名, 才能正确的调用此方法.
覆盖引发动态绑定
考虑上例中: 只有子类覆盖了基类的方法, 才会引发多态. 在动态运行阶段, a.show()被执行时候, 其方法签名若为B对象, 则它事先查看A中是否有show方法, 如果有则进行向下转型, 使用实际的B对象调用show.
如果使用private/final阻止多态的产生, 那程序则通常不会按我们预期的进行执行:
class A {
private void f() {
System.out.println("A f()");
}
public static void main(String[] args) {
A a = new B();
// A f()
a.f();
}
}
public class B extends A{
// @Override
public void f() {
System.out.println("B f()");
}
}
如果我们要强制执行多态, 则最好使用@Override进行强制覆盖.
构造器和多态
构造器的调用顺序
构造器的调用顺序必然是先调用基类构造器, 然后递归下去调用子类构造器.
构造器的主要任务是: 检查对象是否被正确构造. 所以只有父类构造器正确的调用, 才可确保子类的构造器正确的调用.
继承与清理
在继承情况下, 如果需要手动编写清理函数, 那么子类的清理函数必须调用父类的清理函数.
对象的销毁顺序应该和初始化顺序相反. 考虑顺序定义两个对象A,B, 那么在B中是可以引用对象A的. 正确的销毁步骤是调用B的清理函数, 然后在调用A的清理函数.
如果是先调用A的清理函数, 那么在调用B的任何函数时(包括销毁函数), 在B中的A对象是无效的, 则导致异常的发生.
class A {
public void dispose() {
System.out.println("A dispose");
}
}
public class B extends A{
private A a = new A();
public void dispose() {
System.out.println("B dispose.");
a.dispose();
super.dispose();
}
public static void main(String[] args) {
B b = new B();
b.dispose();
}
}
存在一种特殊的情况, 即一个对象被多个对象所共享. 那么需要通过计数来判断什么时候需要清理对象:
class Shared {
private int refcount = 0;
private static long counter = 0;
private final long id = counter++;
public Shared() {
System.out.println("Creating " + this);
}
public void addRef() {
refcount++;
}
public void dispose() {
if (--refcount == 0) {
System.out.println("disposing " + this);
}
}
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) {
System.out.println("Creating " + this);
this.shared = shared;
this.shared.addRef();
}
protected void dispose() {
System.out.println("disposing " + this);
shared.dispose();
}
public String toString() {
return "Composing " + 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),
new Composing(shared)
};
for (Composing c: composing) {
c.dispose();
}
}
}
构造器内部的多态方法的行为
假设class A extends B, 那么在A中使用super来引用B. 但super的类型实际上是A的, 它向上转型为B, 通过强制类型转换(B) super.
这可以解释构造器中的多态行为:
class A {
A() {
System.out.println("A constructor.");
show();
}
public void show() {
System.out.println("A show");
}
}
public class B extends A{
B() {
System.out.println("B constructor.");
}
public void show() {
System.out.println("B show");
}
public static void main(String[] args) {
// A constructor.
// B show
// B constructor.
B b = new B();
}
}