从ENUM类构建说明看Java类的编译加载顺序

 

业务遇到需求,细化三点:信息固定,用枚举记录;不同个体存在先后,枚举类提供公共的“根据特定顺序的个体的list”的方法;部分信息不明,部署时需根据配置文件变更。

借此需求,研究了ENUM的编译加载规则。

涉及的模块按顺序排列如下:

(1)、普通代码块;(2)、构造器;(3)、静态变量/方法;(4)、静态代码块;(5)、普通方法

显然不同于普通类的“先静态代码块、后构造”的流程,原因应该在于“枚举本身就是一种静态常量”,这种“静态”优先于静态方法块的“静态”。同时,实际处理类变量会更复杂些。

5个步骤中,(1)与(2)是真正的对应与“个体”的构造环节,与普通类相比依然是代码块先于构造器被编译,但是这个“个体”更像是“归属于类的变量”,优先级最高;特别的,(1)与(2)构造环节未完成时,枚举类的个体并未被赋值,枚举个体的获取、以及类名XX.values的获取均为null,debug可发现(因此需要将枚举类的实际个体与构造声明相区分,不是执行了调用函数就立即有了对应枚举实际个体)。当然,与普通类相同的是枚举个体声明也只是构造器的调用,而且,代码块也依然是构造器的一部分,“调用一次”就执行一次(区别于静态代码块)。

步骤(3)主要是加载静态变量和方法,方法只是加载进内存,并不执行。自此,后续方法都可以调用之。

步骤(4)执行静态代码块,仅一次。

步骤(5)不必解释。

枚举的对象、方法调用出现问题时,就会出现“枚举类java.lang.ExceptionInInitializerError”的异常报错,且只在初始化的首次报错,后续用到该枚举,只会报“java.lang.NoClassDefFoundError: Could not initialize class xxx”。

 

借此看下Java类的编译加载原理。

首先,回到ENUM类中,步骤(1)与(2)当然不能调用静态变量静态方法,但是可以调用普通变量普通方法。步骤(2)中可以使用this,获取的就是当前枚举个体(但是前述说了不能调用声明变量与枚举类,因为该实体还没挂钩的对应声明上),对应也就可以读取普通变量、方法,均可调用;而通过调用普通方法间接调用静态变量,则会执行时报错,同时出现虚拟机的clinit、init方法。问题来了

clinit是类级别的构造方法,init是构造特定对象个体的构造方法,而现在出现的是clinit中调用了init方法,还是对应于“枚举的个体本质还是个constant实体”的背景。

clinit are the static initialization blocks for the class, and static field initialization.
init is the (or one of the) constructor(s) for the instance, and non-static field initialization.

则clinit是对类与类级别静态方法的初始化,init是对实体、非静态的初始化。一般而言,肯定是先clinit后init,即先静态后非静态,枚举看似不是,但其本质静态属性使其构造环节依然是个“静态过程”,而且是“clinit调用的init”。

另外,父子类的调用流程是:父静态代码块、子静态代码块、父代码块、父构造器、子代码块、子构造器。可见,“静态优先”的原则依然没变,是抽象类级别,无视了父类的非静态的“构造环节”。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值