Java 类的初始化

在 Java 代码中,如果要初始化一个静态字段,可以在声明时直接赋值,也可以在静态代码块中对其赋值。如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记为常量值(ConstantValue),其初始化直接由 Java 虚拟机完成。除此之外的直接赋值操作,以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命名为

类加载的最后一步是初始化,便是为标记为常量值的字段赋值,以及执行 方法的过程。 Java 虚拟机会通过加锁来确保 方法仅被执行一次。 只有当初始化完成之后,类才正式成为可执行的状态。

JVM规范枚举了下述几种情况:

  1. 当虚拟机启动时,初始化用户指定的主类
  2. 当遇到调用 静态方法 的指令时,初始化该静态方法所在的类
  3. 当遇到访问 静态字段 的指令时,初始化该静态字段所在的类
  4. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类
  5. 子类的初始化会触发父类的初始化
  6. 如果一个接口定义了 default 方法,那么直接或者间接实现该接口的类的初始化,会触发该接口的初识化
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类

下面的示例代码逐个演示上述几种情况:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;

public class Main {

    static {
        System.out.println("1. 当虚拟机启动时,初始化用户指定的主类");
    }


    public static void main(String[] args) throws Throwable {

        // Scenario 1,2,3,4
        Scenario2.getInstance();

        // Scenario 5
        new Scenario5();
        // Scenario 6
        new Scenario6Impl();
        // Scenario 7
        Class clazz = Class.forName("Scenario7");
        Method method = clazz.getMethod("doSomething", String.class);
        method.invoke(clazz.newInstance(), "somevalue");

        // Scenario 8
        MethodType mt = MethodType.methodType(void.class, int.class);
        MethodHandle handle = MethodHandles.lookup().findStatic(Scenario8.class,"println", mt);
        handle.invoke(1);
    }
}


class Scenario2 {

    static {
        System.out.println("2. 当遇到调用 静态方法 的指令时,初始化该静态方法所在的类");
    }

    private static class Scenario3 {
        static {
            System.out.println("3. 当遇到访问 静态字段 的指令时,初始化该静态字段所在的类");
        }

        static final Scenario4 INSTANCE = new Scenario4();
    }

    public static Scenario4 getInstance() {
        return Scenario3.INSTANCE;
    }
}

class Scenario4 {
    static {
        System.out.println("4. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类");
    }
}

class Scenario5Parent {
    static {
        System.out.println("5. 1) 子类的初始化会触发父类的初始化");
    }
}

class Scenario5 extends Scenario5Parent {
    static {
        System.out.println("   2) 子类的初始化");
    }
}

interface Scenario6 {

    Scenario6Field field = new Scenario6Field();

    default void doSomething() {

    }

}

class Scenario6Field {
    static {
        // 如果删除接口中的default方法,则不会出发Scenario6Field的初始化,就不会打印下面这条语句
        System.out.println("6. 1) 如果一个接口定义了 default 方法,那么直接或者间接实现该接口的类的初始化,会触发该接口的初识化 (如果删除接口中的default方法,则不会出发Scenario6Field的初始化,就不会打印这条语句)");
    }
}

class Scenario6Impl implements Scenario6 {
    static {
        System.out.println("   2) 初始化接口实现类");
    }
}

class Scenario7 {

    static {
        System.out.println("7. 使用反射 API 对某个类进行反射调用时,初始化这个类");
    }

    public void doSomething(String param) {
    }

}

class Scenario8 {
    static {
        System.out.println("8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类");
    }

    public static void println(int param) {
        System.out.println("    print: " + param);
    }
}

输出结果:

1. 当虚拟机启动时,初始化用户指定的主类
2. 当遇到调用 静态方法 的指令时,初始化该静态方法所在的类
3. 当遇到访问 静态字段 的指令时,初始化该静态字段所在的类
4. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类
5. 1) 子类的初始化会触发父类的初始化
   2) 子类的初始化
6. 1) 如果一个接口定义了 default 方法,那么直接或者间接实现该接口的类的初始化,会触发该接口的初识化 (如果删除接口中的default方法,则不会出发Scenario6Field的初始化,就不会打印这条语句)
   2) 初始化接口实现类
7. 使用反射 API 对某个类进行反射调用时,初始化这个类
8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类
    print: 1

你可能感兴趣的:(Java 类的初始化)