字节码角度分析类初始化过程
init 方法看看空的类的
package com.chi.jvm.clazz; public class InitTest1 { } 0 aload_0 1 invokespecial #1: ()V> 4 return
编译器会自动为类加上一个无参构造器,并调用Object父类的无参构造方法
package com.chi.jvm.clazz; public class InitTest1 { private int a = 1; } // 调用父类构造器 0 aload_0 1 invokespecial #1: ()V> // 初始化实例成员属性 4 aload_0 5 iconst_1 6 putfield #2 9 return
成员变量在默认的构造器方法中初始化,但可以注意到调用父类方法的指令始终放在前面
package com.chi.jvm.clazz; public class InitTest1 { private int a; public InitTest1(){ a = 2; } } // 调用父构造 0 aload_0 1 invokespecial #1: ()V> // 初始化为 1 4 aload_0 5 iconst_1 6 putfield #2 // 赋值为 2 9 aload_0 10 iconst_2 11 putfield #2 14 return
package com.chi.jvm.clazz; public class InitTest1 { private int a; public InitTest1(){ System.out.println(a); // 输出 0 a = 2; } } 0 aload_0 1 invokespecial #1: ()V> 4 getstatic #2 7 aload_0 8 getfield #3 11 invokevirtual #4 14 aload_0 15 iconst_2 16 putfield #3 19 return
如果实例属性字段声明阶段没有初始化,在字节码层面上不会添加默认的初始值,但是通过sout能够正常打印,说明默认值是硬编码到字节码文件中的。
clinit 方法package com.chi.jvm.clazz; public class ClinitTest1 { private static int a; } // 无clint方法静态字段
package com.chi.jvm.clazz; public class ClinitTest1 { private static int a = 1; } // clint 0 iconst_1 1 putstatic #2静态代码块4 return
package com.chi.jvm.clazz; public class ClinitTest1 { private static int a = 1; static { a = 2; } static { a = 3; } } // clinit 方法 0 iconst_1 1 putstatic #24 iconst_2 5 putstatic #2 8 iconst_3 9 putstatic #2 12 return
package com.chi.jvm.clazz; public class ClinitTest2 { static { System.out.println("1"); } static { System.out.println("2"); } public ClinitTest2(){ System.out.println(3); } public static void main(String[] args) { new ClinitTest2(); new ClinitTest2(); } }
静态属性和静态代码块只会初始化一次
final 关键字修饰的字段- 修饰实例属性:必须在声明时初始化,或在构造方法中初始化
- 修饰静态属性:必须在声明时初始化,或在static静态代码块中初始化
- 调用自己:this()
- 调用父类:super()
无论调用哪个构造器,必须放在所有语句之前
- 编译器会提供默认的构造方法,用于调用父类构造方法和初始化实例属性
- 编译器会提供clinit方法初始化静态属性、按顺序执行静态代码块
- 在构造器中调用其他构造器方法必须放在第一句
- final修饰的属性必须进行初始化
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)