10.3 对象的创建与初始化

在Java语言中通过new操作符可以创建出一个Java类的实例对象。除了Object类之外,其他Java类都至少有一个父类。在没有通过extends显式声明时,Object类是默认的父类。在Java类中可以通过构造方法添加对象初始化时的逻辑。在创建对象时,父类和祖先类的初始化逻辑会被依次执行。实际的初始化流程是先沿着继承层次结构树往上传递,完成部分初始化工作。到达Object类之后,再沿着层次结构树向下,完成其余的初始化工作,最后回到最初的Java类。任何一个步骤出现错误,都会导致对象创建失败。

在进行实际的对象创建之前,需要为要创建的对象分配内存空间。所需要的内存空间大小取决于Java类及其父类和祖先类包含的所有实例域的数量和类型。如果内存空间不足,则创建过程会抛出OutOfMemoryError错误。如果内存分配成功,则把新创建的对象的所有实例域都设为默认值,包括Java类本身声明的及父类声明的。这个新创建的对象并不能直接使用,因为类的构造方法还没有被调用。

类的构造方法的调用过程分成三步。第一步是调用父类的构造方法。对父类构造方法的调用分成显式和隐式两种。显式调用是通过super关键词来完成的。如果没有显式地添加对super关键词的调用,则由编译器自动生成相关的代码。第二步是初始化类中实例域的值,按照实例域的出现顺序依次初始化。第三步是执行类的构造方法中的其他代码,完成最终的初始化工作。由于在第一步中会调用父类的构造方法,实际的执行流程会先跳转到父类的构造方法,再沿着继承层次结构树依次往上跳转,直到到达Object类。由于Object类没有父类,不再继续向上传递,而是进行后两步操作。整个过程是一个典型的递归调用过程。

通过代码清单10-4的示例来说明构造方法的调用过程。Animal类是Dog类的父类。在创建Dog类的对象时,Dog类的构造方法先被调用。在Dog类的构造方法中使用参数值“4”调用父类Animal的构造方法。在Animal类的构造方法的最开始,会再调用Animal类的父类Object类的构造方法。由于Object类没有父类,不再需要继续调用父类的构造方法,而是执行自身的构造方法的逻辑。Object类的构造方法执行完成之后,调用流程回到Animal类。会先对Animal类中实例域进行初始化,使legs变量被初始化为0。接着Animal类的构造方法中的其他代码被调用,legs变量的值被设置为4。接着调用流程转入到Dog类的构造方法中,先把实例变量name的值初始化为“<default>”,然后调用Dog类的构造方法中的其他代码。此时就完成了Dog类的对象的创建过程。

代码清单10-4 说明构造方法调用顺序的示例


class Animal{

int legs=0;

Animal(int legs){

this.legs=legs;

}

}

class Dog extends Animal{

String name="<default>";

Dog(){

super(4);

}

}

public class NewObject{

public static void main(String[]args){

Dog dog=new Dog();

System.out.println(dog.legs);//输出值为4

}

}


在编写构造方法时,要注意不要在构造方法中调用可以被子类覆写的方法。这是因为如果子类覆写了该方法,那么在初始化过程中进行到调用父类的构造方法时,父类的构造方法所调用的是子类所覆写的方法,而此时子类的构造方法中的代码并没有被执行,对象仍处于初始化的过程中。这时调用子类的方法,很容易出现错误。代码清单10-5中给出了一个示例,在父类Parent的构造方法中调用getCount方法,在子类Child中覆写了此方法并返回在Child类的构造方法中传入的值。这两个类初看起来似乎并没有问题,但是当试图创建Child类的实例时,会出现除零错误。这是因为在执行Parent类的构造方法时使用的是Child类中的getCount方法,而此时Child类的实例域count还没有被初始化,它的值是0。

代码清单10-5 在父类构造方法中调用子类方法的错误示例


class Parent{

public Parent(){

int average=30/getCount();

}

protected int getCount(){

return 4;

}

}

class Child extends Parent{

private int count;

public Child(int count){

this.count=count;

}

public int getCount(){

return count;

}

}

public class BadConstructor{

public void test(){

Child child=new Child(5);

}

}