5、接口初始化规则和类加载、连接、初始化案例剖析

时间:2021-08-31 17:38:34

5.1、接口初始化规则

在了解接口的初始化规则前,先搞清类的初始化规则。

当java虚拟机初始化一个类时,会先初始化它的所有父类。但是这条规则并不适用于接口。

  • 在初始化一个类时,并不会先初始化它所实现的接口;
  • 在初始化一个接口时,并不会先初始化它的父接口;

  使用一句话总结:实现类或者子接口的初始化并不会导致父接口的初始化

  只有当程序首次主动使用特定接口的静态变量(运行期常量)时,才会导致接口的初始化。这里需要注意的是接口中定义的变量都是常量,而常量又分为编译期常量和运行期常量,编译期常量的值在编译期间就可以确定,直接存储在了调用类的常量池中,所以访问接口中的编译期常量并不会导致接口的初始化,只有访问接口中的运行期常量才会引起接口的初始化。

下面通过代码验证上述结论:

package com.shtec.init;

import java.util.UUID;

/**
 * 在初始化一个类时,并不会先初始化它所实现的接口;
 * 在初始化一个接口时,并不会先初始化它的父接口;
 * @author sunhao
 *
 */
public class Demo3 {
    
    public static void main(String[] args) {
        //在初始化一个类时,并不会先初始化它所实现的接口;
        System.out.println(child3.a);
        //输出: 1
        //没有输出“Parent3 invoked”,说明父接口Parent3并没有被初始化。
        
        //在初始化一个接口时,并不会先初始化它的父接口;
        System.out.println(Parent3.str);
        //输出: 
        //Parent3 invoked
        //a58ee35a-6590-416d-bf6b-ba6976f0d268
    }

}
interface GrandPa{
    //下面这段代码相当于普通类中的“静态代码块”,打印出来就相当于该接口被初始化了;
    public static Thread t = new Thread(){
        {
            System.out.println("GrandPa invoked");
        }
    };
}
interface Parent3 extends GrandPa{
    //运行期常量
    String str = UUID.randomUUID().toString();
    //下面这段代码相当于普通类中的“静态代码块”,打印出来就相当于该接口被初始化了;
    public static Thread t = new Thread(){
        {
            System.out.println("Parent3 invoked");
        }
    };
}
class child3 implements Parent3{
    public static int a = 1;
}

5.2、类加载、连接、初始化案例剖析

下面使用两个案例复习一下类型的加载、连接和初始化过程;

package com.shtec.init;

public class Demo4 {

    public static void main(String[] args) {
        System.out.println("a="   Singleton.a);
        System.out.println("b="   Singleton.b);
        //输出:
        //a=1
        //b=1
    }

}
//定义一个单例类
class Singleton{
    
    public static int a;
    public static int b = 0;
    private static Singleton singleton = new Singleton();
    
    private Singleton(){
        a  ;
        b  ;
    }
    public static Singleton getInstance(){
        return singleton;
    }
}

  如果对上面Demo4的代码输出没有疑问,请继续下面的代码示例:

package com.shtec.init;

/**
 * 分析:
 *         将代码【public static int b = 0;】放在构造方法下面,输出结果为什么变了?
 *         请注意使用“类的加载、连接(验证、准备和解析)和初始化过程分析该案例”;
 *         Singleton在初始化之前,会先经历连接阶段中的准备阶段,准备阶段会为类的【静态变量】分配内存,并将其初始化为【默认值】,
 *                 此时,a=0;singleton=null,b=0;
 *         然后Singleton经历初始化阶段,代码自上而下执行,为类的静态变量赋予正确的初始值
 *                 此时,a=0;
 *                     singleton会创建一个对象,调用Singleton类的构造方法,此时a=1;b=1;
 *                 接着初始化静态变量b,b的值又被更改为0;
 *         所以最终输出:
 *                 //a=1
 *                //b=0
 * @author sunhao
 *
 */
public class Demo4 {

    public static void main(String[] args) {
        System.out.println("a="   Singleton.a);
        System.out.println("b="   Singleton.b);
        //输出:
        //a=1
        //b=0
    }

}
//定义一个单例类
class Singleton{
    
    public static int a;
    
    private static Singleton singleton = new Singleton();
    
    private Singleton(){
        a  ;
        b  ;
    }
    public static int b = 0;//注意,本段代码调动了位置
    
    public static Singleton getInstance(){
        return singleton;
    }
}