1. 引言
在一个的 java 程序中,为了保证程序的健壮性,在初始化一个对象之前, 应该保证对象中的变量都有进行初始化的操作。那么,在对象初始化过程中,变量加载的顺序大致是怎么样的呢?这篇文章主要探讨这个问题,包含普通变量、静态变量、静态代码块 的加载顺序。
2. 普通变量
在类中,变量定义位置的先后会影响到它们的加载顺序,但是,无论如何,它们都会在构造方法执行之前进行初始化,我们来看下面这段代码:
首先创建一个 Book 类,只定义一个有参的构造方法:
public class Book {
public Book(int i){
System.out.println("book" + i +" init ...");
}
}
接着创建一个 BookStore 类,该类中有两个 Book 类的引用,并进行了初始化。注意,这边有意将 book2 写在 book1 上。
public class BookStore {
public Book book2 = new Book(2);
public Book book1 = new Book(1);
public BookStore(){
System.out.println("bookstore init ...");
}
}
最后,在 main 创建一个 BookStore 对象:
public class MainApp {
public static void main(String[] args){
BookStore bookStore = new BookStore();
}
}
最终运行结果如下,从结果可以看出,由于 book2 变量定义在 book1 之前,根据类加载普通变量的顺序,所以在加载时它在 book1 之前。
book2 init …
book1 init …
bookstore init …
3. 静态变量初始化
为了验证静态变量是在什么时候初始化的,我们在 BookStore 中添加一个 static 变量。
public class BookStore {
public Book book2 = new Book(2);
public Book book1 = new Book(1);
public static Book book3 = new Book(3);
public BookStore(){
System.out.println("bookstore init ...");
}
}
在 MainApp 中对 BookStore 进行两次初始化:
public class MainApp {
public static void main(String[] args){
BookStore bookStore = new BookStore();
bookStore = new BookStore();
}
}
运行结果如下,我们可以看出,static 变量在 book1、book2 这两个变量之前进行加载,说明 static 变量是一个类中最先进行初始化的。
之后,MainApp 中又进行了一次初始化,而此时 book3 并没有重新进行加载,说明静态变量只会在第一个 Book 创建时才会进行初始化,之后,静态变量不会再进行初始化操作了。
book3 init …
book2 init …
book1 init …
bookstore init …
book2 init …
book1 init …
bookstore init …
4. 静态代码块
java 中允许将多个静态初始化动作组织成一个特殊的 “静态语句”(有时也叫”静态块“),例如下面这个样子:
public class Test{
int i;
static{
i = 4;
}
}
静态代码块加载的顺序和静态变量加载规律一样,这里就不再进行重复说明。需要注意的是,当静态代码块碰到静态变量时会怎样呢?这就跟普通变量加载顺序是一致的,位置先后影响他们初始化的顺序,这个可以自行做实验进行验证。
5. 总结
通过上面几个简单的小实验,在这里总结一下对象的创建过程,这一部分主要是参考 《java 编程思想》第四版的内容。
我们在加载 Book 类时,
- 即使没有直接标明 static 关键字,构造器实际上也是静态方法。因此,当首次创建类型为 Book 的对象时,或者 Book 的静态变量 / 方法(包括构造方法)首次被访问时,Java 解释器必须查找类路径,以定位 Book.class 文件。
- 然后载入 Book.class,有关静态初始化的所有动作都会执行,因此,静态初始化只在 Class 对象首次被加载时进行一次。
- 当使用 new Book() 创建对象时,首先将在堆上为 Book 对象分配足够的空间
- 这块存储空间会被清零,所有基本数据类型设置为默认值(如 int、float 被设置为 0),引用类型被置为 null
- 执行所有出现于字段定义处的初始化动作
- 执行构造器。如果涉及继承时,这会牵涉到很多动作。
以上就是一个对象加载变量的顺序。