Java class 初始化和实例化练习

这篇博客详细探讨了Java类的初始化时机,包括从JVM和Java文档的角度出发的初始化条件。还讨论了循环依赖的解决方案,并阐述了实例初始化的步骤,强调了不要在父类构造器中调用子类方法。此外,提供了实例初始化的练习题来加深理解,最后讲解了final变量的编译阶段特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类初始化

初始化时机:

初始化是执行 class 或 interface 的初始化方法 <init>, 以下内容来自 JVM 文档 1 :

  • 当执行这些虚拟机指令时: new T, 存取 T 的静态变量/方法
  • new, 静态资源相关的 MethodHandle 实例被调用
  • 一些反射方法被调用. 例如 Class 的方法
  • T(T 是 class) 的子类被初始化
  • T(T 是 interface) 的实现类被初始化
  • T 是JVM启动时的初始化类

以下内容来自 Java 文档 2 :

  • T is a class and an instance of T is created.
  • A static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top level class (§7.6) and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

循环依赖:

package com.lvw.jvm.init_class;

public class CircleInitializing {

    public static void main(String[] args) {
        int f = Sub.f;
    }

    static class Super {
        static int f = 1;   // 1
        static {  // 2
            System.out.println("Super is initializing.");
            Sub.run();  // 3
            System.out.println("Super was initialized.");
        }
        static void run() {
            System.out.println("Super.run() was called.");
        }
    }

    static class Sub extends Super { // 4 recursive init for Super
        static {
            System.out.println("Sub is initializing.");
            Super.run();
            System.out.println("Sub was initialized.");
        }
        static void run() {
            System.out.println("Sub.run() was called.");
        }
    }
}

/*
output:
Super is initializing.
Sub is initializing.
Super.run() was called.
Sub was initialized.
Sub.run() was called.
Super was initialized.
*/

循环依赖的解决方法:

首先, 在 C 的 Class 对象中保存一个状态, 这个状态有四个取值:

  • C 已经验证ok且准备好了, 但未被初始化
  • C 正在被初始化
  • C 已经初始化完成
  • C 初始化失败

其次, 维护一个 Lock, 用来串行化初始化.

对于一个初始化线程, 具体初始步骤为:

  1. 获取 Lock
  2. 如果 C 正在被其他线程初始化, 则 block 自己
  3. #todo

关于循环依赖的原文看这里

碎片补充:

  • 涉及某个类不一定会初始化, 只有静态资源被访问才会导致静态资源所在的类被初始化. 举一个例子: 如果 A 继承 B { static int f = 1;}, 对 A.f 的访问会导致 B 的初始化, 但不会初始化 A.
  • 初始化中的循环依赖的解决方法: 在 Class 对象中
  • static final a = 1; 是编译期常量, 对这种常量的访问不会导致初始化.
  • static final a = random(); 这是运行期常量, 这会导致初始化.

实例初始化

步骤:

  • 递归初始化父类
  • 初始化
    • 初始化 Class
      • 类变量赋值(static) 只执行一次
      • 静态初始化块(static {}) 只执行一次
    • 初始化实例
      • 实例变量赋值(非static) 每个对象执行一次
      • 实例变量初始化块({}) 每个对象执行一次
      • 构造函数 每个对象执行一次

不要在父类的构造方法中调用子类的方法.

实例初始化的练习

设 C 为当前正在实例化的类型. C初始值 = T

1 找到 C 的 Class object

2.a 如果有父类或接口, 先初始化父类和接口 (回到0)

2.b 如果没有父类和接口, 继续 3

3.a 如果还未初始化, 则先初始化

从上到下, 初始化类变量, 执行静态初始化块, 如果遇到 new 或者 静态资源引用了其他类 D, C = D, 回到 1. 否则继续 4.

3.b 如果正在初始化或已经完成初始化, 则继续 4

通过 Class 的状态可以判断出是否正在初始化, 如果是在初始化中, 说明发生了递归, 此时无需再次执行初始化逻辑.

4 为实例变量赋值

遇到 new 回到 0.

5 执行构造函数

如果是在初始化中, 静态变量可能是初始值或者null.

练习1

package com.lvw.jvm.init_class;

class CA {
    private static String ivar = null;
    static CB other = new CB("CA.other of static");
    static {
        System.out.println("in CA init");
        ivar = new String("2");
    }
    CA(String caller) {
        System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
    }
}
class CB {
    public int ivar = 2;
    static {
        System.out.println("in CB init");
    }
    static CA other1 = new CA("CB.other1");
    CA other2 = new CA("CB.other2");
    CB(String caller) {
        System.out.println("CB.CONSTRUCTOR by " + caller);
    }

    public static void main(String[] args) {
        CB cb = new CB("main");
    }
}

开始分析, 先写出第一层

CB cb = new CB("main");
	static{ System.out.println("in CB init"); } // 静态初始化块 步骤 3.a
	static CA other1 = new CA("CB.other1");     // 类变量 步骤 3.a
	CA other2 = new CA("CB.other2");            // 实例变量 步骤 4
	CB <CONSTRUCTOR>  System.out.println("CB.CONSTRUCTOR by " + caller); // 构造方法 步骤 5

然后补充第二层, 第三层…, 每缩进一层相当于回到步骤1

CB cb = new CB("main");
	static{ System.out.println("in CB init"); } // 静态初始化块 3.a
	static CA other1 = new CA("CB.other1");     // 类变量 3.a
		static String ivar = null;  // 类变量 3.a
		static CB other = new CB("CA.other of static");  // 初始化 3.a
			CA other2 = new CA("CB.other2");  // 实例变量 3.b
				CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
			CB <CONSTRUCTOR>  System.out.println("CB.CONSTRUCTOR by " + caller);
		static {System.out.println("in CA init"); ivar = new String("2");}  // 静态初始化块 3.a
		CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar); // 构造方法 5
	CA other2 = new CA("CB.other2");            // 实例变量 4
		CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
	CB <CONSTRUCTOR>  System.out.println("CB.CONSTRUCTOR by " + caller); // 构造方法 5

分析结果: 即把所有 System.out.println 按顺序写出来:

in CB init
CA.CONSTRUCTOR by CB.other2, ivar: null
CB.CONSTRUCTOR by CA.other of static
in CA init
CA.CONSTRUCTOR by CB.other1, ivar: 2
CA.CONSTRUCTOR by CB.other2, ivar: 2
CB.CONSTRUCTOR by main

注意上面的 ivar 变量第一次被引用的时候, 还未被初始化.

练习2

package com.lvw.jvm.init_class;

class CA {
    private static String ivar = null;
    static CB other = new CB("CA.other of static");
    static {
        System.out.println("in CA init");
        ivar = new String("2");
    }
    CA(String caller) {
        System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
    }
}
class CB {
    public int ivar = 2;
    static {
        System.out.println("in CB init");
    }
    CA other1 = new CA("CB.other1"); // 去掉了 static
    CA other2 = new CA("CB.other2");
    CB(String caller) {
        System.out.println("CB.CONSTRUCTOR by " + caller);
    }

    public static void main(String[] args) {
        CB cb = new CB("main");
        // CA ca = new CA("main");
    }
}

分析过程:

CB cb = new CB("main");
	static{ System.out.println("in CB init"); } 
    CA other1 = new CA("CB.other1");
		static String ivar = null;
		static CB other = new CB("CA.other of static");
			CA other1 = new CA("CB.other1");
				CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
			CA other2 = new CA("CB.other2");
				CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
			CB <CONSTRUCTOR>  System.out.println("CB.CONSTRUCTOR by " + caller);
		static {System.out.println("in CA init"); ivar = new String("2");}
		CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
    CA other2 = new CA("CB.other2");
		CA <CONSTRUCTOR> System.out.println("CA.CONSTRUCTOR by " + caller + ", ivar:" + ivar);
	CB <CONSTRUCTOR>  System.out.println("CB.CONSTRUCTOR by " + caller);

分析结果:

in CB init
CA.CONSTRUCTOR by CB.other1, ivar: null
CA.CONSTRUCTOR by CB.other2, ivar: null
CB.CONSTRUCTOR by CA.other of static
in CA init
CA.CONSTRUCTOR by CB.other1, ivar: 2
CA.CONSTRUCTOR by CB.other2, ivar: 2
CB.CONSTRUCTOR by main

final 变量

final在编译阶段完成, 如果修改final类型的常量后不要增量部署, 因为所以使用了这个变量的地方不会修改. (https://blog.youkuaiyun.com/momo_ibeike/article/details/80257552)

appendix

  • dynamic call site : 指的是一次动态调用
  • Call site specifier : 是一个 item, 解释如何链接到给定的 call site


  1. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 ↩︎

  2. https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.1 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值