Java 类初始化(详解)

您所在的位置:网站首页 初始化的概念是指什么意思啊 Java 类初始化(详解)

Java 类初始化(详解)

#Java 类初始化(详解)| 来源: 网络整理| 查看: 265

Java 是一门面向对象的语言,所以除了基本类型以外都是对象,那么这些对象是怎样从 class 文件加载到虚拟机,然后成为内存中一个个对象的呢?本文就来分析下 Java 中的类是如何初始化的?包括类初始化,实例初始化,构造器等。

一、类加载时机

类的生命周期:从被加载到虚拟机内存中开始,到卸载出内存。一共包括如下阶段:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading),其中验证,准备,解析 3 个阶段统称为连接(Linking)。

那么,在什么样的时机下会触发类的生命周期呢?虚拟机规范严格规定了有且只有以下 5 中情况必须立即对类进行初始化(加载,验证,准备自然在初始化之前进行):

遇到 new、getstatic、putstatic或invokestatic这 4 条字节码指令时,如果类未进行过初始化,那么需首先触发类的初始化。分别对应的场景为:1、使用 new 关键字实例化对象时;2、读取类的静态变量时(被 final修饰,已在编译期把结果放入常量池的静态字段除外);3、设置类的静态变量时;4、调用一个类的静态方法时。使用反射对类进行调用时。初始化一个类时,如果其父类还未初始化,那么会先触发其父类的初始化。当虚拟机启动时,被指定为需要执行的那个主类(main() 方法所在的类),虚拟机需要先初始化。JDK 1.7 动态语言支持,详见《深入理解 Java 虚拟机》。

其中 1、new 关键字实例化对象是调用对象的构造方法,构造方法也是静态方法的一种(《Java 编程思想》);4、main 方法也是静态方法,所以触发类初始化的时机就是:调用类的静态对象。

现在,类初始化的时机我们清楚了,那么类是如何初始化的呢?实例又是如何初始化的?涉及继承时又是如何初始化的?

二、类初始化,实例初始化

要搞懂类初始化的过程,就要搞懂类初始化和实例初始化。

类初始化 :就是类第一次加载到内存时进行的过程,也就是我们在上一节讨论的内容。类初始化只进行一次(前提是被同一类加载器加载),后续使用 new 等实例化对象时都不在进行初始化了,所以类初始化只运行一次。初始化的都是属于类(而不是实例)的内容(静态),所以对所有实例共享。实例初始化 :也就是实例化对象时每次都会进行的过程,初始化属于实例的内容(非静态),没有实例所拥有的实例内容是不共享的,独有的。 2.1 类初始化

前面已经讨论了类进行初始化的时机,下面就来说说类初始化会干些什么。

在类初始化之前的准备阶段,虚拟机会将类变量(static 修饰的变量)分配内存并设置零值。

在类初始化阶段,执行类构造器 () 方法。 类初始化方法有如下特点:

编译器会在将 .java 文件编译成 .class 文件时,收集所有类初始化代码和 static {} 域的代码,收集在一起成为 () 方法;子类初始化时会首先调用父类的 () 方法;JVM 会保证 () 方法的线程安全,保证同一时间只有一个线程执行; 2.2 实例初始化

构造器:保证实例正确的初始化,能被使用。

所以,既然子类继承了父类,那么子类调用构造函数初始化的,就需要调用父类的构造器,不管是显示调用父类构造器还是 JVM 自动调用,这样才能保证子类正确的被构造。

那么,实例初始化的过程到底是如何的呢?

JVM 收集实例初始化变量和 {} 域组合成实例初始化方法 ();实例初始化时首先执行 () 方法,然后执行构造函数;子类通过构造函数构造实例时会首先调用父类的 () 方法和父类的构造函数,如果没有显示调用父类的构造函数,那么 JVM 会自动调用父类的无参构造函数,保证父类构造函数一定被调用,然后再是子类自己的 () 方法和构造函数;至此,实例就构造完毕了;

现在,大概应该清楚类和实例如何初始化和被构造了:

父类类初始化 ();子类类初始化 ();父类 () + 父类构造器;子类 () + 子类构造器; 三、示例:组合的初始化 package acherie.resuing; class Window { static { System.out.println("Window static"); } Window (int marker) { System.out.println("Window(" + marker + ")"); } } class House { static { System.out.println("House static"); } Window w1 = new Window(1);// Before constructor House () { System.out.println("House()"); w3 = new Window(33); } Window w2 = new Window(2); void f() { System.out.println("f()"); } Window w3 = new Window(3); } public class OrderOfInitialization { public static void main(String[] args) { House h = new House(); h.f(); } }

运行结果如下:

House static Window static Window(1) Window(2) Window(3) House() Window(33) f() Process finished with exit code 0

其实,使用反编译软件(如:jd-gui,或 intellij idea 自带的)看一看编译后生成的代码,很多问题就清楚了:

// House.class package acherie.resuing; import acherie.resuing.Window; class House { Window w1 = new Window(1); Window w2 = new Window(2); Window w3 = new Window(3); House() { System.out.println("House()"); this.w3 = new Window(33); } void f() { System.out.println("f()"); } static { System.out.println("House static"); } } // OrderOfInitialization.class package acherie.resuing; import acherie.resuing.House; public class OrderOfInitialization { public OrderOfInitialization() { } public static void main(String[] args) { House h = new House(); h.f(); } } 四、示例:继承的初始化 package acherie.resuing; class Person { private int i = 8; protected int j; static { System.out.println("Person 静态初始化子句"); } { System.out.println("Person 实例初始化子句"); } Person () { System.out.println("Person()"); } Person(int i) { System.out.println("Person(int), i=" + i); } int k = printInit("Person.k 初始化"); static int m = printInit("static Person.m 初始化"); { System.out.println("Person 后置实例初始化语句"); } static int printInit(String s) { System.out.println(s); return 47; } } public class Student extends Person { Student () { super(1); System.out.println("Student()"); } Student(int i) { this(); System.out.println("Student(int), i=" + i); } public static void main(String[] args) { new Person(3); System.out.println("--------------------------"); Student student = new Student(2); } public static int marker = printInit("Student.marker 初始化"); { System.out.println("Student 实例初始化域"); } }

运行结果如下:

Person 静态初始化子句 static Person.m 初始化 Student.marker 初始化 Person 实例初始化子句 Person.k 初始化 Person 后置实例初始化语句 Person(int), i=3 -------------------------- Person 实例初始化子句 Person.k 初始化 Person 后置实例初始化语句 Person(int), i=1 Student 实例初始化域 Student() Student(int), i=2 Process finished with exit code 0

使用反编译软件查看编译后的 class 文件:

// Person.class package acherie.resuing; class Person { private int i = 8; protected int j; int k; static int m; Person() { System.out.println("Person 实例初始化子句"); this.k = printInit("Person.k 初始化"); System.out.println("Person 后置实例初始化语句"); System.out.println("Person()"); } Person(int i) { System.out.println("Person 实例初始化子句"); this.k = printInit("Person.k 初始化"); System.out.println("Person 后置实例初始化语句"); System.out.println("Person(int), i=" + i); } static int printInit(String s) { System.out.println(s); return 47; } static { System.out.println("Person 静态初始化子句"); m = printInit("static Person.m 初始化"); } } // Student.class package acherie.resuing; import acherie.resuing.Person; public class Student extends Person { public static int marker = printInit("Student.marker 初始化"); Student() { super(1); System.out.println("Student 实例初始化域"); System.out.println("Student()"); } Student(int i) { this(); System.out.println("Student(int), i=" + i); } public static void main(String[] args) { new Person(3); System.out.println("--------------------------"); new Student(2); } } 六、总结

本文主要介绍了类和示例如何初始化的,并辅以示例。其实,在研究这个问题时最大的感受在于,通过查看编译后生成的 class 文件,其实很多不懂的东西都能够解决和更好的理解。

七、参考链接 《深入理解 Java 虚拟机》Java 101: Class and object initialization in Java


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3