原文地址:http://java.dzone.com/articles/instance-initializers-java
英文标题:Java Instance Initializers in Java Explained
初始化很重要,因为一直以来,没有初始化的数据都是bug的主要来源。例如,由于未初始化的数据导致的错误经常出现在C代码中,因为C没有内置的机制强制实现适当的数据初始化。C程序员必须总是记住在分配了内存之后要初始化数据,然后再使用。对比之下,Java语言拥有内置的机制来帮助你确保恰当地初始化新创建对象占据的存储空间。通过恰当地使用这种机制,你可以防止你的设计中创建的某个对象进入无效的初始状态。
Java语言拥有三种专门用来保证恰当地进行对象初始化的机制:实例初始化(也称为实例初始化块),实例变量初始化和构造器。(实例初始化和实例变量初始化统称为“initializers”)所有三种机制都会在对象创建时自动执行。当你使用new操作为新对象分配内存或者Class类的newInstance()方法,java虚拟机会确保在你使用新分配的存储空间之前执行初始化代码。如果你设计了类并且初始化和构造器总是令新创建对象产生有效状态,就不会有任何人可以创建和使用还没有恰当地初始化的对象。
当Java创建对象时,按照如下顺序来初始化对象:
- 设置成员变量为默认值(0,false,null)
- 调用对象的构造方法(但是不执行构造方法体)
- 执行父类的构造方法
- 使用初始化和初始化块来初始化变量
- 执行构造方法体
让我们看一个例子。
public class InstanceInitializers1 {
int i = 10;
{
System.out.println(i);
i = 8;
}
public static void main(String[] args) {
new InstanceInitializers1();
}
}
正如你所料,输出结果是10。
- 初始化代码必须捕获异常;
- 执行某些无法用实例变量初始化表达式表示的运算。
当然,你也可以将这些代码写入到构造器中。但是在一个拥有多个构造器的类中,你将不得不在每个构造其中重复这些代码。使用实例初始化,你可以只写一遍代码,无论哪个构造器用来创建对象,它都会执行。实例初始化在匿名内部类中也很有用,因为匿名内部类无法声明构造器。
实例初始化内部的代码可能不会返回。除了匿名内部类,实例初始化只有在类的每个构造器都显示抛出已检查异常的情况下才能抛出异常。而匿名内部类的实例初始化可以抛出任何异常。当你写了初始化代码(无论实例变量初始化还是实例初始化),你必须确定没有引用任何处于正在初始化的变量后面声明的实例变量。也就是说,你不能在initializer中进行前向引用。如果你不遵守这条规则,编译器会给你一个错误信息并拒绝生成class文件。当一个对象被创建,initializers按文本顺序执行--在源码中出现的先后顺序。这条规则防止initializers使用还没有被初始化的实例变量。
实例变量首先被初始化为默认值,然后实例初始化块才执行,但是我们不能进行前向引用(forwark reference)。如果你违反了这条规则,编译器会给出报错信息并且拒绝产生class文件。例如:
public class InstanceInitializers2 {
{
System.out.println(i);
i = 8;
}
int i = 10;
public static void main(String[] args) {
new InstanceInitializers1();
}
}
因为前向引用,上面的代码无法通过编译。但是,用下面的代码片,你可以看到前向引用和默认值初始化。
public class Initializers1 {
int j = getI();
int i = 10;
public static void main(String[] args) {
System.out.println(new Initializers1().j);
}
public int getI() {
return i;
}
}
你认为输出会是什么?
会打印出0。因为当getI方法被调用的时候,i的值是0(默认值)。
实例初始化块可以抛出异常,但是每个构造器都必须声明抛出指定的异常或者其父类。让我们看一个例子。
import java.io.*;
public class InitializerException {
{
if(true) {
System.out.println("A");
throw new FileNotFoundException();//A checked exception
}
}
public InitializerException() throws FileNotFoundException {
}
public static void main(String[] args) throws Exception {
new InitializerException();
}
}
如果实例初始化不能正常完成会导致编译时错误。如果具名类的实例初始化可以抛出一个被检查异常,会引起编译时错误,除非异常或者其父类被显示声明在每个构造器上并且类至少有一个显示声明的构造器。匿名类中的实例初始化可以抛出任何异常。
我们需要一个if(true)[代码片,因为JLS上说明:“一个break、continue、return或者throw语句无法正常完成”,所以我们使用if来避免无法通过编译。