类初始化和实例化
三种机制:声明赋值,初始化块,构造函数
两种类型:静态,非静态
一种关系:父子
触发场景
类初始化触发条件
静态方法调用
非常量静态域访问或赋值
动态类加载Class.forName
实例化对象前如果尚未加载
获取对象的常规方式,类实例初始化触发条件
new关键字生成新实例
反射newInstance
获取实例的特殊方式,底层直接二进制组装,不会执行构造
机制间顺序
类初始化过程:声明赋值(静态) <-> 初始化块(静态)
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Field { public Field (String str) { System.out.println(str); } } class Clazz { public static Field field1 = new Field("static member" ); static { System.out.println("static block" ); } public static Field field2 = new Field("static member" ); public static void method () { } } public class Test { public static void main (String[] args) { Clazz.method(); } }
类实例化过程:声明赋值 <-> 初始化块 -> 构造函数
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Field { public Field (String str) { System.out.println(str); } } class Clazz { public Clazz () { System.out.println("constructor" ); } private Field field1 = new Field("member" ); { System.out.println("block" ); } private Field field2 = new Field("member" ); } public class Test { public static void main (String[] args) { new Clazz(); } }
总结
不管是否静态,声明赋值和初始化块取决于书写顺序
实例构造过程,构造函数最后被执行
类型间顺序
静态 > 非静态
先类加载才能实例化,静态过程只在类加载时执行,实例过程可以执行多次,每个实例独立
特殊过程,静态成员实例化自己,在静态初始化过程中穿插实例化
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Test { public static void main (String[] args) { staticFunction(); } static Test st = new Test(); static { System.out.println("1" ); } { System.out.println("2" ); } Test() { System.out.println("3" ); System.out.println("a=" + a + ",b=" + b); } public static void staticFunction () { System.out.println("4" ); } int a = 5 ; static int b = 6 ; }
父子关系
子类对象实例化过程
先加载父并静态初始化
再加载子类并静态初始化
然后构建父类
最后构建子类
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Field { public Field (String str) { System.out.println(str); } } class Parent { public static Field field1 = new Field("parent static member" ); private Field field2 = new Field("parent member" ); static { System.out.println("parent static block" ); } { System.out.println("parent block" ); } public Parent () { System.out.println("parent constructor" ); } } class Child extends Parent { public static Field field1 = new Field("child static member" ); private Field field2 = new Field("child member" ); static { System.out.println("child static block" ); } { System.out.println("child block" ); } public Child () { System.out.println("child constructor" ); } } public class Test { public static void main (String[] args) { new Child(); } }
不会触发初始化的情况
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Field { public Field (String str) { System.out.println(str); } } class Clazz { public static Field field1 = new Field("static member" ); private Field field2 = new Field("member" ); static { System.out.println("static block" ); } { System.out.println("block" ); } public Clazz () { System.out.println("constructor" ); } } public class Test { public static void main (String[] args) throws ClassNotFoundException { Class.forName("test.Clazz" , false , Test.class.getClassLoader()); } }
访问静态常量,不会初始化
编译时常量(基本类型或字面量字符串,以及它们的运算符表达式),编译过程中就会发生常量折叠,变量被直接替换成值
程序使用第三方包的常量,如果第三方修改了常量值,直接更换新版依赖,结果还是旧值,这是因为编译后直接被替换成值,不会再去包内读取,感知不到变化,程序代码必须重新编译才行
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Field { public Field (String str) { System.out.println(str); } } class Parent { public static final int field = 0 ; public static Field member = new Field("parent static member" ); static { System.out.println("static block parent" ); } } class Child extends Parent { public static final int field = 1 ; public static Field member = new Field("child static member" ); static { System.out.println("static block child" ); } } public class Test { public static void main (String[] args) { System.out.println(Child.field); System.out.println(Parent.field); } }
通过子类访问父类静态变量,并不会初始化子类,本质上和子类无关
Sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Parent { public static int field = 0 ; static { System.out.println("static block parent" ); } } class Child extends Parent { static { System.out.println("static block child" ); Parent.field = 1 ; } } public class Test { public static void main (String[] args) { System.out.println(Child.field); } }
构造函数事项
构造函数中不应调用可被覆写的方法
原本父类调用自身方法进行初始化,但是子类复写后,变成调用子类的方法,有可能使父类行为异常
构造函数间可以重载相互调用