【Java类加载】<init>方法与<clinit>方法

1,030 阅读2分钟

先看一个小Demo,然后再对比分析<init><clinit>方法的区别

Demo

public class Demo {
    private static Demo instance;
    static {
        System.out.println("static代码块开始");
        // 下面这句编译器报错,非法向前引用x与y
        // System.out.println("x=" + x);
        instance = new Demo();
        System.out.println("static代码块结束");
    }
    public Demo() {
        System.out.println("constructor开始");
        // 构造器可以访问声明于他们后面的静态变量
        System.out.println("x=" + x");
        // 此时 x=0
        x++;
        System.out.println("constructor结束");
    }

    public static int x = 9;

    public static Demo getInstance() {
      return instance;
    }
    public static void main(String[] args) {
        Demo demo = Demo.getInstance();
        System.out.println("x=" + demo.x);
        System.out.println("y=" + demo.y);
    }
}

运行结果:

static代码块开始
constructor开始
x=0
x=1
constructor结束
static代码块结束
x=9

疑惑点

  1. 为什么静态代码块不能向前引用静态变量xy
  2. 为什么构造方法可以引用静态变量xy

答:init is the (or one of the) constructor(s) for the instance, and non-static field initialization. clinit are the static initialization blocks for the class, and static field initialization. 上面这两句是StackOverflow上的解析,翻译过来就是:

  • <init>()方法instance实例是对非静态变量解析初始化,而<clinit>()方法class类构造器对静态变量,静态代码块进行初始化。
  • 补充一点:<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{...})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,以及在它之后的变量,在前面的静态语句块中可以对静态变量进行赋值但不能访问(引用)。看一下下面的例子
public class Test{
  static{
    i = 0;  //给变量赋值可以正常通过
    System.out.println(i);  //这句编译器会提示“非法向前引用”
  }
  static int i = 1;
}

结合案例来说

对于第一个问题:

  • JVM首先执行的是类加载初始化过程中的 <clinit>() 方法,也就是加载静态变量以及静态代码块(static{...},x,y
  • static{...}中不能事先引用显示引用变量x/y,上文有提到.

对于第二个问题:

  • <clinit>()方法中触发了对象的初始化(<init>()方法),那么会继续执行<init>()方法
  • 执行<init>()方法完成之后,再继续执行<clinit>()方法。
  • 最后public static int x = 9;重新给类变量x赋值为 9,因此,最后输出x=9