Java - Constructors and Polymorphism

来源:互联网 发布:it was not until1920 编辑:程序博客网 时间:2024/04/30 23:28

构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的)。但仍需要理解构造器怎样通过多态在复杂的层次结构中运作。

基类构造器总是在导出类的构造过程中被调用,而且按照继承层次逐层链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊任务:检查对象是否被正确的构造。导出类只能访问它自己的成员,不能访问基类的成员(类中成员变量通常是private类型)。只有基类的构造器才具有权限对自己的元素进行初始化。因此必须令所有构造器都得到调用,否则就不可能正确构造完整对象。

在导出类的构造器主体中,如果没有明确指定调用某个基类构造器,它就会调用缺省构造器(若某个类没有构造器,编译器会自动合成出一个缺省构造器)

/**
 * Title: Order of constructor calls<br>
 * Description: Order of constructor calls<br>
 * Company: Augmentum Inc<br>
 * Copyright: 2008 (c) Thinking in Java<br>
 * 
@author Forest He
 * 
@version 1.0
 
*/

package com.augmentum.foresthe;
class Meal 
    
public Meal() { System.out.println("class Meal"); }
}

class Bread 
    
public Bread() { System.out.println("class Bread"); }
}

class Cheese 
    
public Cheese() { System.out.println("class Cheese"); }
}

class Lettuce 
    
public Lettuce() { System.out.println("class Lettuce"); }
}

class Lunch extends Meal 
    
public Lunch() { System.out.println("class Lunch"); }
}

class PortableLunch extends Lunch 
    
public PortableLunch() { System.out.println("class PortableLunch"); }
}

public class Sandwich extends PortableLunch {
    
private Bread bread = new Bread();
    
private Cheese cheese = new Cheese();
    
private Lettuce lettuce = new Lettuce();
    
public Sandwich() { System.out.println("class Sandwich"); }
    
public static void main(String[] args) {
        
new Sandwich();
    }

}

 构造器调用顺序

1.调用基类构造器(这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,直到最底层的导出类)

2.按声明顺序调用成员的初始化方法;

3.调用导出类构造器的主体。

这个顺序很重要,因为当进行继承时,我们必须已知道基类的一切,并且可以访问基类中任何声明为publicprotected的成员。

继承和清理

通过组合和继承来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理。如果确实遇到清理的问题,那么必须用心为新类创建dispose()方法(在这里我选用此名称,读者可以提出更好的)。并且由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法。

当覆盖被继承类的dispose()方法时,务必记住调用基类版本的dispose()方法;否则基类的清理动作就不会发生。ps:用super.dispose()调用基类的dispose()方法。基类构造器是Java内部机制的一部分,会自动调用执行,可dispose()方法是我们自己定义的,不会自动执行,所以要求手动调用。

如果某个子对象依赖于其他对象,销毁的顺序应该和初始化顺序相反。对于基类,应该首先对其导出类进行清理,然后才是基类。

构造器内部的多态方法的行为

如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,那么会发生什么情况呢?非构造器的一般方法内部的动态绑定机制前面已经有了详细论述,但构造器内部的动态绑定机制却有不同:如果要调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义,然而此时导出类(拥有覆盖后定义的类)可能并未构造,所以产生的效果相当难以预料。

从概念上讲,构造器的工作实际上是创建对象。在任何构造器内部,整个对象可能只是部分形成:我们只知道基类对象已经进行初始化,但却不知道哪些类是从我们这里继承而来的。然而,一个动态绑定的方法调用却会向内深入到继承层次结构内部,它可以调用导出类中的方法。如果我们是在构造器内部这样做,那么就可能会调用某个方法,而这个方法所操纵的成员可能还未进行初始化,这肯定会招致灾难。

/**
 * Title: Constructors and polymorphism<br>
 * Description: Constructors and polymorphism<br>
 * Company: Augmentum Inc<br>
 * Copyright: 2008 (c) Thinking in Java<br>
 * 
@author Forest He
 * 
@version 1.0
 
*/

package com.augmentum.foresthe;
abstract class Glyph {
    
public abstract void 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 radius) {
        
this.radius = radius;
        draw();
    }

    
public void draw() { System.out.println("RoundGlyph.draw(), radius = " + radius); }
}

public class PolyConstructors {
    
public static void main(String[] args) {
        
new RoundGlyph(5);
    }

}

/* 从输出结果可以看出, Glyph()中的draw方法调用的是导出类中的draw(), 为什么调用的不是基类的draw()方法呢(如果基类的draw()不是abstract的)?
 * 因为在main()中, 我们写了new RoundGlyph(5). 如果我们写new Glyph(), 便会调用基类的draw()方法了.
 
*/

Glyph中,draw()方法是抽象的,这样设计是为了覆盖该方法。我们确实在RoundGlyph中强制覆盖draw()。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,这看起来似乎是我们的目的。但是看输出结果,我们会发现当Glyph的构造器调用draw()时,radius不是默认初始值1,而是0 

其实前面讲述的初始化顺序并不十分完整,初始化的实际过程应该是:

1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零(数值初始化为0,对象初始化为null)

2.如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0

3.按照声明的顺序调用成员的初始化方法;

4.调用导出类的构造器主体。

为防止上述那种可能发生的潜在错误,编写构造器时有一条有效的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属于final方法)。这些方法不能被覆盖,因此也就不会出现上述令人惊讶的问题。