7.2对象的生命周期

7.2对象的生命周期
一旦一个类被装载、连接和初始化,它就随时可以使用了。程序可以访问它的静态字段, 调用它的静态方法,或者创建它的实例。本节会讨论类的实例化和初始化,即对象生命起始阶 段的活动;还要讨论垃圾收集和终结,即对象生命尽头的活动。

7.2.1类实例化

在Java程序中,类可以被明确或者隐含地实例化。实例化一个类有四种途径:明确地使用 new操作符;调用Class或者java.lang.reflect.Constructor对象的newlnstance ()方法;调用任何 现有对象的clone ()方法;或者通过java.io.ObjectlnputStream类的getObject ()方法反序列化。 下面的例子中演示了其中三种创建新的类实例的方法:

除了这四种在Java源代码中明确地实例化对象的方法之外,还有几种情况下对象会被隐含地 实例化,即在源代码中看不见明确的new、newInstance ( )、clone ()或者OhjectlnputStream.readObject ( )。

在任何Java程序中第一个隐含实例化对象可能就是保存命令行参数的String对象。每一个命 令行参数都会有一个String对象的引用,把它们组织成一个String数组并作为一个参数传递到每 一个程序的main ()方法中。

另外两种隐含实例化类的方法和类装载的过程有关。首先,对于Java虚拟机装载的每一个类 型,它会暗中实例化一个Class对象来代表这个类型。其次,当Java虚拟机装载了在常量池中包 含CONSTANT_String_info人口的类的时候,它会创建新的String对象的实例来表示这些常量字 符串。把方法区中的CONSTANT_String_info人口转换成一个堆中的String实例的过程是常量池 解析过程的一部分,这个过程将在第8章详细描述。

还有一条隐含创建对象的途径是通过执行包含字符串连接操作符的表达式产生对象。如果 这样的字符串不是一个编译时常量,用于中间处理的String和StringBuffer对象会在计算表达式的 过程中创建。下面是一个例子:

// On CD-ROM in file classlife/ex5/Example5.java
class Example5 {

public static void main(String[] args) {

if (args.length < 2) {
System.out.println("Must enter any two args.");
return;
}

System.out.println(args[0] + args[1]);
}
}

Example5的main ()方法的字节码包含了三个隐含创建的String对象和一个StringBuffer对 象。其中两个String对象的引用作为传递到main ()方法的args数组的一部分,是通过位于偏移 量为24和33的aaload指令压人找的。StringBuffer是在偏移量为18的new指令创建的,被偏移量为 28的invokespecial指令初始化。最后一个String对象代表args[0]和args[1]的连接,是通过调用 StringBuffer对象的toString ()方法建立的,这是由位于偏移量为37的invokevirtual指令完成的。 当Java虚拟机创建一个类的新实例时,不管是明确的还是隐含的,首先都霈要在堆中为保存对象的实例变量分配内存。所有在对象的类中和它的超类中声明的变量(包括隐藏的实例变量) 都要分配内存。在第5章中描述过,堆中对象的映像中其他一些与实现相关的元素,比如指向方 法区屮类数据的指针,大致也是在这个时间分配的。一旦虚拟机为新的对象准备好了堆内存,它 立即把实例变量初始化为默认的初始值。这和在表7-1中为类变量陚予的默认初始值是一样的。

—旦虚拟机完成了为新对象分配内存和为实例变量赋默认初始值后,它随后就会为实例变 量陚正确的初始值。根据创建对象的方法不同,Java虚拟机使用三种技术之一来完成这个工作。 如果对象是通过clone ()调用来创建的,虚拟机把原来被克隆的实例变量中的值拷贝到新对象 中。如果对象是通过一个ObjectlnputStream的readObject ()调用反序列化的,虚拟机通过从输 入流中读人的值来初始化那些非暂时性的实例变量。否则,虚拟机调用对象的实例初始化方法。 实例初始化方法把对象的实例变量初始化为正确的初始值。

Java编译器为它编译的每一个类都至少生成一个实例初始化方法。在java的class文件中, 这个实例初始化方法被称为“<init>”。针对源代码中每一个类的构造方法,Java编译器都产生 一个<init> ()方法。如果类没有明确地声明任何构造方法,编译器默认产生一个无参数的构造 方法,它仅仅调用超类的无参数构造方法。和其他的构造方法一样,编译器在class文件中创建 一个<init> ()方法,对应它的默认构造方法。

一个<init> ()方法中可能包含三种代码:调用另一个<init> ()方法,实现对任何实例变 量的初始化,构造方法体的代码。如果构造方法通过明确地调用同一个类中的另一个构造方法 (―个this ()调用)开始,它对应的<init> ()方法由两部分组成:

• 一个同类的<init> ()方法的调用。
•实现了对应构造方法的方法体的字节码。

如果构造方法不是通过一个this ()调用开始的,而且这个对象不是Object, <init> ()方法 则由三部分组成:

•一个超类的<init> ()方法的调用。

•任意实例变量初始化方法的字节码。

•实现了对应构造方法的方法体的字节码。

如果构造方法没有使用this ()调用开始,并且这个类是Object,上面列表中的第一个元素就 不存在。因为Object没有超类,它的<init> ()方法就不能通过调用超类的()方法开始。

如果构造方法通过明确地调用超类的构造方法(一个super ()调用)开始,它的<init> () 方法会调用对应的超类的<init> ()方法。比如,如果一个构造方法通过明确地调用“super (int, String)构造方法”开始,对应的<init> ()方法会从调用超类"init(int, String)"方法幵始。如果构造方法没有明确地从this ()或者super ()调用开始,对应的<init> ()方法 默认会调用超类的无参数<init> ()方法。

下面的例子包含了三个构造方法,编号从1到3:

// On CD-ROM in file classlife/ex6/Example6.java
class Example6 {

private int width = 3;

// Constructor one:
// This constructor begins with a this() constructor invocation,
// which gets compiled to a same-class <init>() method
// invocation.
Example6() {
this(1);
System.out.println("Example6(), width = " + width);
}

// Constructor two:
// This constructor begins with no explicit invocation of another
// constructor, so it will get compiled to an <init>() method
// that begins with an invocation of the superclass's no-arg
// <init>() method.
Example6(int width) {
this.width = width;
System.out.println("Example6(int), width = " + width);
}

// Constructor three:
// This constructor begins with super(), an explicit invocation
// of the superclass's no-arg constructor. Its <init>() method
// will begin with an invocation of the superclass's no-arg
// <init>() method.
Example6(String msg) {
super();
System.out.println("Example6(String), width = " + width);
System.out.println(msg);
}

public static void main(String[] args) {
String msg
= "The Agapanthus is also known as Lily of the Nile.";
Example6 one = new Example6();
Example6 two = new Example6(2);
Example6 three = new Example6(msg);
}
}

执行后,Example6程序打印出如下输出:

Example6(int), width=1
Example6(), width=1
Example6(int), width=2
Example6(String), width=3
The Agapanthus is also known as Lily of the Mile.
注意这个对应第一个构造方法的<init> ()方法从一个同类的<init> ()方法的调用开始, 然后执行对应的构造方法体。因为构造方法从一个this ()调用开始,它对应<init> ()方法 不包含任何实例变量初始化方法的字节码。


对应第二个构造方法的<init> ()方法包含三部分。第一部分是一个对超类(Object)的无 参数<init>()方法的调用。编译器默认生成这个调用,因为在第二个构造方法的方法体中第一 条语句并不是一个明确的super ()调用。随后是第二个部分一width实例变量初始化方法的字 节码。<init> ()方法包含的第三部分是第二个构造方法的方法体的字节码。


第三个构造方法的<init> ()方法和第二个构造方法的<init> ()方法一样,都有三个部分: —个对超类的<init> ()的调用,width初始化方法的字节码,构造方法体的字节码。第二个构 造方法和第三个构造方法的区别是,第二个构造方法没有用一个明确的this ()或者super ()调 用开始。因此,编译器在第二个构造方法的<init> ()方法中默认放置了一个超类的无参数 <init> ()方法的调用。而第三个构造方法使用一个明确的super ()调用开始,编译器就在对应 的<init> ()方法中把它转换成对应的、超类的<init> ()方法的调用。

对于除Object以外的每一个类,不管是同类的还是直接超类的,<init> ()方法都必须从另 一个<init> ()方法调用开始。<init> ()方法不允许捕捉由它们所调用的<init> ()方法抛出的 任何异常。比如,如果子类的<init> ()方法调用一个被意外中止的超类的<init> ()方法,那 么子类的<init> ()方法也必须同样被意外中止。

猜你喜欢

转载自www.cnblogs.com/mongotea/p/11979546.html