下面这个程序有两个不可变的值类(value class),值类即其实例表示值的类。第一个类用整数坐标来表示平面上的一个点,第二个类在此基础上添加了一点颜色。主程序将创建和打印第二个类的一个实例。那么,下面的程序将打印出什么呢? class Point { protected final int x, y. private final String name. // Cached at construction time Point(int x, int y) { this.x = x. this.y = y. name = makeName(). }
protected String makeName() { return "[" x "," y "]". } public final String toString() { return name. } }
public class ColorPoint extends Point { private final String color. ColorPoint(int x, int y, String color) { super(x, y). this.color = color. } protected String makeName() { return super.makeName() ":" color. } public static void main(String[] args) { System.out.println(new ColorPoint(4, 2, "purple")). } }
main方法创建并打印了一个ColorPoint实例。println方法调用了该ColorPoint实例的toString方法,这个方法是在Point中定义的。toString方法将直接返回name域的值,这个值是通过调用makeName方法在Point的构造器中被初始化的。对于一个Point实例来说,makeName方法将返回[x,y]形式的字符串。对于一个ColorPoint实例来说,makeName方法被覆写为返回[x,y]:color形式的字符串。在本例中,x是4,y是2,color的purple,因此程序将打印[4,2]:purple,对吗?不,如果你运行该程序,就会发现它打印的是[4,2]:null。这个程序出什么问题了呢? 这个程序遭遇了实例初始化顺序这一问题。要理解该程序,我们就需要详细跟踪该程序的执行过程。下面是该程序注释过的版本的列表,用来引导我们了解其执行顺序: class Point { protected final int x, y. private final String name. // Cached at construction time Point(int x, int y) { this.x = x. this.y = y. name = makeName(). // 3. Invoke subclass method }
protected String makeName() { return "[" x "," y "]". } public final String toString() { return name. } }
public class ColorPoint extends Point { private final String color. ColorPoint(int x, int y, String color) { super(x, y). // 2. Chain to Point constructor this.color = color. // 5. Initialize blank final-Too late }