类的初始化的深入认识
要认识类的初始化,首先说一下,类的加载。
类的加载分五个阶段:加载阶段,验证阶段,准备阶段,解析阶段,初始化阶段。
这五个阶段具体做什么这里就不详细说了。类文件加载文章详细说了。
初始化JVM做了什么?
初始化阶段是执行类构造器<clinit>()方法的过程。(注意这里的类构造器和我们通常所说的类的构造方法是不一样的,构造方法用于实例化一个对象)
举个例子:这里SupClass()是构造方法。用于实例化一个对象。
public class SupClass {
public static int age = 18;
static{
System.out.println("Come In SuperClass!");
}
public SupClass() {//类的构造方法
}
}
什么是类构造器<clinit>()方法呢?
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块合并组成。
所有可以理解为:执行<clinit>()方法就是对类所有类变量赋值和执行静态语句块的过程。
JVM严格规定了有且只有一下五种情况必须立即对类进行“初始化”。
1.遇到new,getstatic,putstatic,invokestatic这四个字节指令时,如果类没有进行初始化过,必先初始化。
对于这个四个指令常见的java代码场景有:
1.使用new关键字实例化对象时。2.读取、设置一个类的静态字段时。注意:小知识(笔试面试常遇到)当静态字段被final修饰时,在编译期间结果会放入常量池静态字段,在读取、设置时不需要初始化类了。还有如果static修饰的属性是在父类中,通过子类读取,设置不初始化子类,只初始化父类。举个例子:
package com.example.demo.test;
public class SubClass {
static {
System.out.println("Come In SubClass!");
}
public static int realage=16;
}
package com.example.demo.moth;
import com.example.demo.test.SubClass;
import org.junit.Test;
public class TestMoth {
@Test
public void test1(){
System.out.println(SubClass.realage);
}
}
输出结果:
如果初始化过了,就不再初始化了。例如:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
@Test
public void test1(){
SuperClass superClass = new SuperClass();
System.out.println(SuperClass.age);
}
结果:
为了更明显下面再演示了两个:
@Test
public void test1(){
SuperClass superClass = new SuperClass();
// System.out.println(SuperClass.age);
}
@Test
public void test1(){
// SuperClass superClass = new SuperClass();
System.out.println(SuperClass.age);
}
当realage是final修饰时:
package com.example.demo.test;
public class SubClass {
static {
System.out.println("Come In SubClass!");
}
public static final int realage=16;
}
在这里额外插一个小知识。在使用idea开发工具时,第一次测试的时候,SuperClass类的age没有有final修饰,第二次测试的时候改成final修饰。会出现一下现象第一次测试时:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
public class TestMoth {
@Test
public void test1(){
System.out.println(SuperClass.age);
}
}
测试结果:
.class文件:正常!改成final修饰之后:
public class SuperClass {
public static final int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
public class TestMoth {
@Test
public void test1(){
System.out.println(SuperClass.age);
}
}
执行结果:
这就不正常了!理论上是不会打印Come In SuperClass!这句的。为何呢?现在我们分析一下,由于age被static和final修饰理论上在编译期间age就会被加载到常量池,可以通过类直接调用而不用初始化了。那这就是idea工具编译的时候有问题了。(这个现象在eclipse中没有出现)此时打开对应的.class文件:你会发现没有编译到我们修改后的代码。这是由于idea执行的时候是不会自动编译我们的代码。第一次执行它编译成了一个.class文件,第二次的时候由于已经生产了一个它不会再自动去编译。所以导致我们的代码没有生效。此时我们手动编译一下再run:
你会发现结果正常了!
打开.class文件:
一切正常!3.调用一个类的静态方法时。
public class SuperClass {
public static final int age = 18;
public static final String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
@Test
public void test2(){
System.out.println(SuperClass.getAge());
}
第二点的例子:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("Come In SubClass!");
}
}
@Test
public void test3(){
System.out.println(SubClass.age);
}
输出结果:
2.使用java.lang.reflect包的方法对类进行反射调用时,没有被初始化过,必先初始化。
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
@Test
public void test2() throws Exception{
Class classType = Class.forName("com.example.demo.test.SuperClass");
Object obj = classType.newInstance();
System.out.println("使用反射反射机制创建出来的对象是否是SuperClass类的对象:" + (obj instanceof SuperClass));
}
Class类中对应的很多方法都是调用java.lang.reflect包的方法。
如newInstance方法。这里就不多说了。自己去研究了。
结果:
3.当初始化一个类时,父类类没有比初始化,必先初始化父类。
举个例子:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("Come In SubClass!");
}
}
@Test
public void test4(){
SubClass subClass = new SubClass();
}
4.当指定一个要执行的类(主类,包含main方法的类),JVM会自动优先初始化这个类。
举个例子:
public class TestMain {
static{
System.out.println("static");
}
public static void main(String[] args) {
System.out.println(SuperClass.age);
}
}
public class SuperClass {
public static final int age = 18;
public static final String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
5.当使用JDK7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是:
REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,同时方法对应对应的实体类没有初始化时先初始化类。
案例待补充!