第十一章 对象的生命周期
11.1 创建对象的方式
在Java程序中,对象可以被显式或隐式地创建,创建一个对象就是指构造一个类的实例。前提条件是这个类已经被初始化。
有4种创建对象的方法:
用new语句创建对象,这是最常见的创建对象的方式。
运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
调用对象的clone()方法。
运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
Object类的clone()方法具有以下特点:
声明为protected类型,Object的子类如果希望对外公开clone()方法,必须扩大访问权限。
如果Java类没有实现Cloneable接口,那么clong方法会抛出ClongNotSupportException异常。
Object类在clone方法的实现中会创建一个复制的对象,这个对象与原来的对象具有不同的内存地址,不过它们的属性值相同。
在程序中还可以隐式的创建对象,包括以下几种情况:
对于Java命令中的每个命令行参数,Java虚拟机会创建相应的String对象,并把他们组织到一个String数组中,再把它作为参数传给程序入口main(String args[])方法。
程序代码中的String类型的直接数对应一个String对象。
字符串操作符“+”的运算结果为一个新的String对象。
当Java虚拟机加载一个类时,会隐含地创建描述这个类的Class实例
Java虚拟机创建一个对象包含以步骤:
给对象分配内存
将对象的实例变量自动初始化为其变量类型的默认值
初始化对象。再初始化过程中主要负责给实例变量赋予正确的初始值
如果对象是通过clone方法创建的,那么Java虚拟机把原来被克隆对象的实例变量的值复制到新对象中
如果对象是通过ObjectInputStream类的readObject()方法创建的,那么Java虚拟机通过从输入流中读入的序列化数据来初始化那些非暂时性的实例变量。
在其它情况下,如果实例变量在声明时被显式的初始化,那就 把初始化值赋给实例变量,接着再执行构造方法。
11.2 构造方法
构造方法必须满足以下语法规则:
方法名必须与类名相同
不要声明返回类型
不能被static、final、synchronized、abstract和native修饰。
11.2.1 重载构造方法
用this语句来调用其它构造方法时,必须遵守以下语法规则:
假如在一个构造方法中使用了this语句,那么它必须作为构造方法的第一条语句(不考虑注释语句)
只能在一个构造方法中用this语句来调用类的其它构造方法,而不能在实例方法中用this语句来调用类的其它构造方法。
只能用this语句来调用其它构造方法,而不能通过方法名来直接调用构造方法。
11.2.2 默认构造方法
默认构造方法是没有参数的构造方法,可分为两种:隐含的默认构造方法和程序显式定义的默认构造方法。
在Java语言中,每个类至少有一个构造方法,如果用户定义的类中没有提供任何构造方法,那么Java语言将自动提供一个隐含的默认构造方法。该方法没有参数,用public修饰,且方法体为空。
如果类中显式定义了一个或多个构造方法,并且所有的构造方法都带有参数,那么这个类就失去了默认构造方法。
11.2.3 子类调用父类的构造方法
父类的构造方法不能被子类继承。
在子类的构造方法中,可以通过super关键字调用父类的构造方法。
用super语句来调用父类的构造方法时,必须遵守以下规则:
在子类的构造方法中,不能直接通过父类方法名调用父类的构造方法,而是要使用super语句
假如在子类的构造方法中有super语句,它必须作为构造方法的第一条语句。
在创建子类对象时,Java虚拟机首先会执行父类的构造方法,然后再执行子类的构造方法。在多级继承的情况下,将从继承树的最上层开始,依次执行各个类的构造方法。这可以保证子类对象从所有直接或间接父类中继承的实例变量都被正确的初始化。
当子类的构造方法没有用super语句显式调用父类的构造方法时,那么通过这个构造创建子类对象时,Java虚拟机会自动调用父类的默认构造方法。
当子类的构造方法没有用super语句显式调用父类的构造方法时,而父类 又没有提供默认构造方法时,将会出现编译错误。
11.2.4 构造方法的作用域
构造方法只能通过以下方式被调用:
当前类的其它构造方法通过this语句调用它。
当前类的子类的构造方法通过super语句调用它。
在程序中通过new语句调用它。
11.2.5 构造方法的访问级别
构造方法可处于public、protected、默认和private这4种访问级别。
当构造方法处于private级别时,意味着只能在当前类中访问它:在当前类的其它构造方法中通过this关键字调用它,此外还可以在当前类的成员方法中通过new语句调用它。
在以下场合中,可以把类的所有构造方法都声明为private类。
在这个类中仅仅包含一些供其它程序调用的静态方法,没有任何实例方法。
禁止这个类被继承。
11.3 静态工厂方法
静态工厂方法与用new语句调用的构造方法相比,有以下区别:
构造方法的名字必须与类名相同,这一特性的优点是符合Java语句的规范,缺点是类的所有重载的构造方法的名字都相同,不能从名字上分辨每个构造方法,容易引起混淆。
每次执行new语句时,都会创建一个新的对象,而静态工厂方法每次被调用的时候,是否会创建一个新的对象完全取决于方法的实现。
new语句只能创建当前类的实例,而静态工厂方法可以返回当前类的子类的实例。
静态工厂方法最主要的特点是:每次被调用时,不一定要创建一个新的对象。
静态工厂方法可以用来创建以下类的实例:
单例类:只有唯一的实例的类
枚举类:实例的数量有限的类。
具有实例缓存的类:能把已经创建的实例暂且存放在缓存中的类。
具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。
11.3.1 单例类
在系统中具有唯一性的组件可以作为单例类,这种类的实例通常会占用较多的内存,或者实例的初始化过程比较冗长,因此随意创建这些类的实例会影响系统的性能。
实现单例类有两种方法:
把构造方法定义为private类型,提供public static final类型的静态常量,该常量引用类的唯一实例。
把构造方法定义为private类型,提供public static类型的静态工厂方法,优点是可以灵活地决定如果创建类的实例。
11.3.2 枚举类
在创建枚举类时,可以考虑以下设计模式:
把构造方法定义为private类型。
提供一些public static final类型的静态变量,每个静态变量引用类的一个实例。
如果需要的话,提供静态工厂方法,允许用户根据特定参数获得与之匹配的实例。
11.3.3 不可变类与可变类
不可变类:指当创建了这个类的实例后,就不允许修改它的属性值。
用户在创建不可变类时,可以考虑采用以下的设计模式:
把属性定义为private final类型
不对外公开用于修改属性的setXXXXX()方法
只对外公开用于读取属性的getXXXXXX()方法
在构造方法中初始化所有属性
覆盖Object类的equals()和hashCode()方法
如果需要的话,提供实例缓存和静态工厂方法,允许用户根据特定参数获得与之匹配的实例
可变类:实例的属性允许修改。
11.3.4 具有实例缓存的不可变类
通常,只有满足以下条件的不可变类才需要实例缓存:
不可变类的实例的数量有限
在程序运行过程中,需要频繁访问不可变类的一些特定实例。这些实例拥有与程序本身同样长的生命周期。
11.3.5 松耦合的系统接口
11.4 垃圾回收
垃圾回收有以下优点:
把程序员从复杂的内存追踪、监测和释放等工作中解放出来,减轻程序员进行内存管理的负担。
防止系统内存被非法释放,从而使系统更加健壮和稳定。
垃圾回收具有以下特点:
只有当对象不再被程序中的任何引用变量引用时,它的内存才可能被回收。
程序无法迫使垃圾回收器立即执行垃圾回收操作。
当垃圾回收器要回收无用对象的内存时,先调用该对象的finalize()方法,该方法有可能使对象复活,导致垃圾回收器取消回收该对象的内存。
11.4.1 对象的可触及性
在Java虚拟机的垃圾回收器看来,堆区中的每个对象都有可能处于以下3个状态之一:
可触及状态:当一个对象被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态。
可复活状态:当程序不再有任何引用变量引用Sample对象时,那么它就进入可复活状态
不可触及状态:当Java虚拟机执行完所有可复活对象的fianlize方法后,假如这些方法都没有使Sample对象转到可触及状态,那么Sample对象就进入不可触及状态。
11.4.2 垃圾回收的时间
如果一个对象不处于可触及状态,就可以称它为无用对象,程序不会持有无用对象的引用,不会再使用它,这样的对象可以被垃圾回收器回收。
一个对象的生命周期从被创建开始,到不再被任何变量引用结束。
11.4.3 对象的finalize方法简介
当垃圾回收器将要释放无用对象的内存时,会先调用该对象的finalize方法,如果再程序终止之前垃圾回收器没有执行垃圾回收操作,那么垃圾回收器将始终不会调用无用对象的finalize方法。
11.4.4 对象的finalize方法的特点
对象的finalize方法具有以下特点:
垃圾回收器是否会执行该方法,以及何时执行该方法,都是不确定的
finalize方法有可能使对象复活,使它恢复到可触及状态
垃圾回收器再执行finalize方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常运行。
11.4.5 比较finalize方法和finally代码块
11.5 清除过期的对象引用
11.6 对象的强、软、弱和虚引用
强引用:垃圾回收器绝不会回收它,当内存空间不足时,Java虚拟机会抛出OutOfMemoryError错误,使程序异常终止。
软引用:如果内存空间足够,垃圾回收器就不会回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。
弱引用:
虚引用: