先来一道面试题
class Grandpa {
static {
System.out.println("爷爷在静态代码块");
}
}
class Father extends Grandpa {
static {
System.out.println("爸爸在静态代码块");
}
public static int factor = 25;
public Father() {
System.out.println("我是爸爸~");
}
}
class Son extends Father {
static {
System.out.println("儿子在静态代码块");
}
public Son() {
System.out.println("我是儿子~");
}
}
public class InitializationDemo {
public static void main(String[] args) {
System.out.println("爸爸的岁数:" + Son.factor);
}
}
输出结果是什么???
爷爷的静态代码块
爸爸的静态代码块
爸爸的岁数:25
对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块)。因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化(如果类 Father 当前还没有初始化),而不会触发子类的初始化
对于一开始的题目,现在我们可以确认 Son.factor
只会触发父类 Father 的初始化,Father 又会触发父类 Grandpa 的初始化(如果类 Grandpa 当前还没有初始化),因此:
先触发类 Grandpa 初始化,输出 爷爷在静态代码块
再触发类 Father 初始化,输出 爸爸在静态代码块
最后输出 爸爸的岁数:25
补充:如果此时再次调用 System.out.println("爸爸的岁数:" + Son.factor);
,此时输出结果:
爷爷的静态代码块
爸爸的静态代码块
爸爸的岁数:25
爸爸的岁数:25
类只会在没有被初始化时才进行初始化,即类的初始化只进行一次
一、类的生命周期
七个阶段:加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
1.1 加载:
通过一个类的全限定名,来获取此类的二进制字节码
将字节码所代表的静态存储结果转化为方法区的运行时数据结构
在 Java 堆中生成一个代表这个类的 Class 对象,作为方法区这些数据的访问路口
1.2 验证:
JVM 规范校验:文件格式验证、元数据验证、字节码验证、符号引用验证
1.3 准备
正式为静态变量分配内存并设置初始值(各数据类型的零值),这些内存在方法区中分配;静态变量的字段属性为常量(被 final 修饰)时,则会初始化为指定的值,而不是零值,类的成员变量的分配内存在初始化阶段才开始
1.4 解析
将符号引用替换为直接引用(直接指向目标的指针、相对偏移量)
1.5 初始化
为类的静态变量赋指定的初始值,对类对象进行初始化
1.6 使用
使用类之前,对类进行实例化,new 或反射,只有实例化了,才能通过对象的引用来访问对象的实例
1.7 卸载
执行垃圾回收
二、类初始化和对象初始化
2.1 类初始化方法
编译器会按照其出现顺序,收集类变量的赋值语句、静态代码块,最终组成类初始化方法。类初始化方法一般在类初始化的时候执行
2.2 对象初始化方法
编译器会按照其出现顺序,收集成员变量的赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法。对象初始化方法一般在实例化类对象的时候执行
再来一题
class Son extends Father {
static Son instance = new Son();
static {
System.out.println("static Son");
}
{
System.out.println("unStatic Son");
}
public Son() {
System.out.println("construct Son");
}
protected void method() {
System.out.println("method Son");
}
@Override
public String toString() {
return "toString Son";
}
}
class Father {
static final String TAG = "Father";
static {
System.out.println("static Father");
}
{
System.out.println("unStatic Father");
}
public Father() {
System.out.println("construct Father");
method();
}
protected void method() {
System.out.println("method Father");
}
@Override
public String toString() {
return "toString Father";
}
}
public class Demo {
// 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类
public static void main(String[] args) {
System.out.println(Son.instance);
}
}
输出结果:
// Son 对象的初始化会先触发父类 Father 的类初始化
static Father
// 父类 Father 对象的初始化
unStatic Father
construct Father
method Son
// Son 对象的初始化
unStatic Son
construct Son
// Son 类的初始化
static Son
// main() 方法输出,调用 Son 的 toString() 方法
toString Son
调用 Son.instance
,其实调用的是 static Son instance = new Son();
,所以会对 Son 进行类初始化,又因为父类 Father 还没有进行类初始化,所以会首先触发父类 Father 的类初始化,然后回到子类 Son,又因为在声明静态变量 instance 的同时,进行了 new Son()
操作,所以会先触发父类 Father 的对象初始化,然后进行子类 Son 的对象初始化,由 Son 的类初始化的顺序(代码顺序:静态代码块在静态字段之后)可知,此时会调用子类 Son 的静态代码块,最后执行 main() 方法所在类的 main() 方法中的输出,调用 Son 的 toString() 方法
问题来了:为什么 Son 类的初始化输出语句在 Son 对象的初始化输出语句的后面输出
其实,Son 的类初始化顺序为:
// 先执行初始化
static Son instance = new Son();
// 后执行初始化
static {
System.out.println("static Son");
}
静态代码块和静态变量的执行顺序是根据代码编写先后顺序来的,而静态变量是又进行了 Son 对象的初始化操作,所以又会触发父类 Father 的类初始化(如果当前 Father 没有初始化)和对象的初始化
如果把 Son 的类初始化顺序改为:
// 先执行初始化(先输出 static Son)
static {
System.out.println("static Son");
}
// 后执行初始化(对 Son 进行对象的初始化)
static Son instance = new Son();
那么整个输出结果:
// Son 对象的初始化会先触发父类 Father 的类初始化
static Father
// Son 类的初始化(在 Son 对象的初始化前)
static Son
// 父类 Father 对象的初始化
unStatic Father
construct Father
method Son
// Son 对象的初始化(在 Son 类的初始化后)
unStatic Son
construct Son
// main() 方法输出,调用 Son 的 toString() 方法
toString Son
最后一题
public class Book {
public static void main(String[] args) {
staticFunction();
}
static Book book = new Book();
static {
System.out.println("书的静态代码块");
}
{
System.out.println("书的普通代码块");
}
Book() {
System.out.println("书的构造方法");
System.out.println("price=" + price +",amount=" + amount);
}
public static void staticFunction(){
System.out.println("书的静态方法");
}
int price = 110;
static int amount = 112;
}
输出结果:
// 对 main() 方法所在的 Book 类进行类初始化(按顺序执行),代码顺序:静态变量在静态代码块之前
// 步骤一:静态变量
// 步骤 1.1:普通代码块
书的普通代码块
// 步骤 1.2:构造方法(对 Book 类进行对象初始化)
书的构造方法
// 步骤 1.3:因为 price 是类的成员变量,在类初始化时被赋值,即当前值为 110,而 amount 是类静态变量,因为对 amount 的赋值语句在静态代码块之后执行,所以此时 amount 的值还是 0
price:110,amount:0
// 步骤二:静态代码块
// 步骤 2.1:静态代码块(对 Book 类进行对象初始化)
书的静态代码块
// main() 方法输出,调用 Book 的 staticFunction() 静态方法
书的静态方法