详解java中的static关键字

相信有不少同学遇到过这类问题,可能查过资料之后接着就忘了,再次遇到还是答不对。接下来通过4个步骤,带大家拆解一下这段代码的执行顺序,并借此总结规律。


1.编译器优化了啥?
下面两段代码对比一下编译前后的变化:

编译前的Child.java
image.png

public class Child extends Parent {

    static {

        System.out.println("Child static initial block");

    }

    {

        System.out.println("Child initial block");

    }

     

    private Hobby hobby = new Hobby();

     

    public Child() {

        System.out.println("Child constructor block");

    }

}

编译后的Child.class

image.png
public class Child extends Parent {

    private Hobby hobby;

 

    public Child() {

        System.out.println("Child initial block");

        this.hobby = new Hobby();

        System.out.println("Child constructor block");

    }

 

    static {

        System.out.println("Child static initial block");

    }

}

通过对比可以看到,编译器把初始化块和实例字段的赋值操作,移动到了构造函数代码之前,并且保留了相关代码的先后顺序。事实上,如果构造函数有多个,初始化代码也会被复制多份移动过去。

据此可以得出第一条优先级顺序:

初始化代码 > 构造函数代码
2.static 有啥作用?
类的加载过程可粗略分为三个阶段:加载 -> 链接 -> 初始化

初始化阶段可被8种情况周志明》P359 "触发类初始化的8种情况")触发:

使用 new 关键字实例化对象的时候

读取或设置一个类型的静态字段(常量")除外)

调用一个类型的静态方法

使用反射调用类的时候

当初始化类的时候,如果发现父类还没有进行过初始化,则先触发其父类初始化

虚拟机启动时,会先初始化主类(包含main()方法的那个类)

当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

如果接口中定义了默认方法(default 修饰的接口方法),该接口的实现类发生了初始化,则该接口要在其之前被初始化

其中的2,3条目是被static代码触发的。

其实初始化阶段就是执行类构造器 方法的过程,这个方法是编译器自动生成的,里面收集了static修饰的所有类变量的赋值动作和静态语句块(static{} 块),并且保留这些代码出现的先后顺序。

根据条目5,JVM 会保证在子类的方法执行前,父类的方法已经执行完毕。

小结一下:访问类变量或静态方法,会触发类的初始化,而类的初始化就是执行,也就是执行 static 修饰的赋值动作和static{}块,并且 JVM 保证先执行父类初始化,再执行子类初始化。

由此得出第二条优先级顺序:

父类的static代码 > 子类的static代码
3.static 代码只执行一次
我们都知道,static代码(静态方法除外)只执行一次。

你有没有想过,这个机制是如何保证的呢?

答案是:双亲委派模型。

JDK8 及之前的双亲委派模型是:

应用程序类加载器 → 扩展类加载器 → 启动类加载器

平时开发中写的类,默认都是由 应用程序类加载器加载,它会委派给其父类:扩展类加载器。而扩展类加载器又会委派给其父类:启动类加载器。只有当父类加载器反馈无法完成这个加载请求时,子加载器才会尝试自己去完成加载,这个过程就是双亲委派。三者的父子关系并不是通过继承,而是通过组合模式实现的。

该过程的实现也很简单,下面展示关键实现代码:

image.png

protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException

{

    // 首先检查该类是否被加载过

    // 如果加载过,直接返回该类

    Class c = findLoadedClass(name);

    if (c == null) {

        try {

            if (parent != null) {

                c = parent.loadClass(name, false);

            } else {

                c = findBootstrapClassOrNull(name);

            }

        } catch (ClassNotFoundException e) {

            // 如果父类抛出ClassNotFoundException

            // 说明父类无法完成加载请求

        }

 

        if (c == null) {

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: