Java基础(05)——类加载顺序

单个类加载顺序

InitOrderClass:

public class InitOrderClass {
    static {
        System.out.println("初始化静态代码块");
    }
    static int staticNum = initStaticNum();

    {
        System.out.println("初始化非静态代码块");
    }
    public int num = initNum();

    public static int initStaticNum(){
        System.out.println("初始化静态字段");
        return 1;
    }

    public int initNum(){
        System.out.println("初始化非静态变量");
        return 2;
    }

    public InitOrderClass(){
        System.out.println("初始化无参构造函数");
    }
}

InitOrderClassEx:

public class InitOrderClassEx {
    static int staticNum = initStaticNum();
    static {
        System.out.println("初始化静态代码块");
    }

    public int num = initNum();
    {
        System.out.println("初始化非静态代码块");
    }

    public static int initStaticNum(){
        System.out.println("初始化静态字段");
        return 1;
    }

    public int initNum(){
        System.out.println("初始化非静态变量");
        return 2;
    }

    public InitOrderClassEx(){
        System.out.println("初始化无参构造函数");
    }
}

InitOrderClass与InitOrderClassEx的区别是:

  • InitOrderClass中静态代码块在静态字段前,非静态变量在非静态代码块前
  • InitOrderClassEx中静态代码块在静态字段后,非静态变量在非静态代码块后

客户端代码:

public class ClientClass {
    public static void main(String[] args) {
        InitOrderClass initOrderClass = new InitOrderClass();
        System.out.println("---------------");
        InitOrderClassEx initOrderClassEx = new InitOrderClassEx();
        System.out.println("---------------");
        initOrderClass = new InitOrderClass();
    }
}

运行结果:

初始化静态代码块
初始化静态字段
初始化非静态代码块
初始化非静态变量
初始化无参构造函数
---------------
初始化静态字段
初始化静态代码块
初始化非静态变量
初始化非静态代码块
初始化无参构造函数
---------------
初始化非静态代码块
初始化非静态变量
初始化无参构造函数

从运行结果可以看到:

  1. 类初始化顺序为:静态代码块/静态字段–>非静态代码块/非静态变量–>构造函数;
  2. 静态代码块与静态字段的初始化顺序由声明顺序决定,非静态代码块与非静态变量的初始化顺序也是由声明顺序决定;
  3. 静态代码块与静态字段的初始化,只在Class对象首次加载的时候进行一次

父子类加载顺序

类加载顺序,主要考察静态与非静态代码,父类与子类的加载顺序。

父类代码:

public class SuperClass {
    static int staticNum = initSuperStaticNum();
    static {
        System.out.println("父类静态代码块");
    }

    int num = initSuperNum();
    {
        System.out.println("父类非静态代码块");
    }

    public static int initSuperStaticNum(){
        System.out.println("父类静态字段");
        return 1;
    }

    public int initSuperNum(){
        System.out.println("父类非静态变量");
        return 2;
    }

    public SuperClass(){
        System.out.println("父类无参构造函数");
    }
}

子类代码:

public class ExtendClass extends SuperClass{
    static int staticNum = initExtendStaticNum();
    static {
        System.out.println("子类静态代码块");
    }

    int num = initExtendNum();
    {
        System.out.println("子类非静态代码块");
    }

    public static int initExtendStaticNum(){
        System.out.println("子类静态字段");
        return 3;
    }

    public int initExtendNum(){
        System.out.println("子类非静态字段");
        return 4;
    }

    public ExtendClass(){
        System.out.println("子类无参构造函数");
    }
}

客户端代码:

public class ClientClass {
    public static void main(String[] args) {
        new ExtendClass();
    }
}

运行结果:

父类静态字段
父类静态代码块
子类静态字段
子类静态代码块
父类非静态变量
父类非静态代码块
父类无参构造函数
子类非静态字段
子类非静态代码块
子类无参构造函数

整体的加载顺序为:先父类后子类,先静态后非静态

具体加载顺序如下:

  1. 父类静态字段/静态代码块–>子类静态字段/静态代码块–>父类非静态变量/非静态代码块–>父类构造函数–>子类非静态变量/非静态代码块–>子类构造函数
  2. 静态字段与静态代码块、非静态变量与非静态代码块之间的顺序同样由声明顺序决定

几种不会触发初始化实例的情况

情况一

通过子类引用父类的静态字段,不会导致子类初始化。

class SuperClass01 {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

class SubClass extends SuperClass01 {
    static {
        System.out.println("SubClass init!");
    }
}

public class TestClass {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

结果如下:

SuperClass init!
123

可以看到,父类被初始化,并取到相应的值,但是子类并没有被初始化。

情况二

通过数组定义来引用类,不会触发此类的初始化

class SuperClass01 {
    static {
        System.out.println("SuperClass init!");
    }
    public static int value = 123;
}

public class TestClass {
    public static void main(String[] args) {
        SuperClass01[] subClasses = new SuperClass01[10];
    }
}

运行后,控制台没有输出,SuperClass01并没有被初始化。

情况三

常量**(static final修饰)**在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

class ConstClass {
    static {
        System.out.println("ConstClass init");
    }
    static final String HELLOWORLD = "hello world";
}

public class TestClass {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);
    }
}

结果如下:

hello world

通过输出结果可以看到,ConstClass类并没有被初始化。因为在编译的时候,常量**(static final修饰)**会存入调用类的常量池中(此处是指main函数所在的类的常量池),在调用时本质上并没有引用到定义此常量的类,而是直接访问了自己的常量池。

static注意事项

  • 静态变量在静态代码块之前声明,静态代码块中对此静态变量既可以赋值,也可以访问

  • 静态变量在静态代码块之后声明,静态代码块中对此静态变量只能赋值,不可以访问

public class StaticTestClass {
    static int value01 = 1;
    static {
        value01 = 2;
        value02 = 3;
        System.out.println(value01);
        System.out.println(value02); // 报错:Illegal forward reference,非法前向引用
    }
    static int value02 = 4;
}

静态变量与静态方法调用

当调用目标类的静态变量或静态方法时,不会触发该类的代码块或构造方法的执行。

Handler类:

class Handler {
    static int value = 1;
    static {
        System.err.println("静态代码块");
    }
    static void print(){
        System.err.println("静态方法");
    }
    {
        System.err.println("非静态代码块");
    }
    public Handler(){
        System.err.println("构造函数");
    }
}

测试类1:

public class TestClass {
    public static void main(String[] args) {
        System.out.println(Handler.value);
    }
}

测试类1结果如下:

静态代码块
1

测试类2:

public class TestClass {
    public static void main(String[] args) {
        Handler.print();
    }
}

测试类2结果如下:

静态代码块
静态方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值