java 枚举循环依赖时初始化问题

今天遇到一个问题,就是设计了两个枚举,一个是状态枚举(EnumA)一个是动作枚举(EnumB),状态枚举定义了当前状态的可以进行的操作,操作枚举定义了执行了此操作后的下一个状态。


具体代码如下:

public enum EnumA {
    STATUS_A("STATUS_A", "状态A", EnumB.OPERATION_A),
    STATUS_B("STATUS_B", "状态B", null);

    private String                             code;
    private String                             desc;
    private List<EnumB>                        operation;
}
EnumA(String code, String desc, List<EnumB> operation) {
        this.operation = operation;
        this.code = code;
        this.desc = desc;
    }
public enum EnumB {
    OPERATION_A("OPERATION_A", "操作1", EnumA.STATUS_B);

    private String                             code;
    private String                             desc;
    private EnumA                              next;
}
EnumB(String code, String desc, EnumA next) {
        this.next = next;
        this.code = code;
        this.desc = desc;
    }

当我按照这种定义去声明枚举值常量时,编译器并没有错误提示。


但是执行的时候结果如下:

 public static void main(String[] args) {
        System.out.println(EnumB.OPERATION_A.getNext());
        System.out.println(EnumA.STATUS_A.getOperation());
    }
image.png
 public static void main(String[] args) {
        System.out.println(EnumA.STATUS_A.getOperation());
        System.out.println(EnumB.OPERATION_A.getNext());
    }
image.png

可以看出来,首先被使用到的枚举值中的内容是完整的。原因简单分析一下便容易明白,当第一个枚举值(例如是EnumA)被使用的时候,虚拟机查到这个EnumA的实例还没有加载,便调用构造函数去实例化。构造函数中需要EnumB的某个实例,所以虚拟机会调用EnumB的构造函数去实例化EnumB,但是此时EnumA还没有实例化完成,拿到的指针为NULL,所以EnumB中的类型为EnumA的属性便为NULL。


解决方法

对症下药的想法就是将两个枚举的关系在初始化层面解除耦合,但是带来的问题是两个枚举又需要使用对方的实例,什么时候将对方的实例填充进去呢?初步想法是当首先被调用的枚举实例化完成后将此枚举的实例填入另外一个枚举属性中。

那第二个问题便是如何让首先使用的枚举实例化后自动的开始填充动作呢?映入脑海的应该是代码块了,它是类加载的时候会自动执行的代码逻辑。所以是否可以采用在构造函数下方添加一个代码块来让枚举实例化(调用构造函数)后执行相应的代码呢?


具体代码如下:

public enum EnumA {
    STATUS_A("STATUS_A", "状态A", EnumB.OPERATION_A),
    STATUS_B("STATUS_B", "状态A", null);
    private String code;
    private String desc;
    private EnumB  operation;

    EnumA(String code, String desc, EnumB next) {
        this.code = code;
        this.desc = desc;
        this.operation = next;
    }

    {
        EnumB.OPERATION_A.setNext(STATUS_B);
    }
}

但是添加了代码后,编译器明显的提示像是对我说:“小伙子想的太简单了”。

image.png

这段代码有一个警告和一个编译错误,他们的内容分别如下:
image.png

image.png

这里说明了,枚举实例是静态的,就是和静态成员变量(用static修饰的变量)是同一个级别的。但是他们的初始化又是利用构造函数才可以完成的,执行了构造函数则必然会执行代码块。因为实例化要一个一个执行,所以代码块执行的时候其他的某些枚举实例还没有创建,所以需要使用静态代码块来完成填充的动作。
所以正确的方式如下:但是并不是完美解决了,需要看文章最后一部分的说明

public enum EnumA {
    STATUS_A("STATUS_A", "状态A", EnumB.OPERATION_A),
    STATUS_B("STATUS_B", "状态A", null);
    private String code;
    private String desc;
    private EnumB  operation;

    static {
        EnumB.OPERATION_A.setNext(STATUS_B);
    }

    EnumA(String code, String desc, EnumB next) {
        this.code = code;
        this.desc = desc;
        this.operation = next;
    }
}

枚举实例加载顺序

这里做了一个实验,可以观察到枚举实例在实例化时的执行顺序:

public enum EnumA {
    STATUS_A("STATUS_A", "状态A", EnumB.OPERATION_A),
    STATUS_B("STATUS_B", "状态A", null);
    private String code;
    private String desc;
    private EnumB  operation;

    static {

        System.out.println("static:");
    }

    EnumA(String code, String desc, EnumB next) {
        this.code = code;
        this.desc = desc;
        this.operation = next;
        System.out.println("constructor:" + this);
    }

    {
        System.out.println("init:" + this);
    }

}
image.png

可见在实例化的时候,构造函数和代码块依次执行。当全部枚举实例都实例化完成后静态代码块才会执行,逻辑比较简单,毕竟枚举实例属于静态变量,当前的状态还是处于类级别初始化阶段,只不过初始化要用到具体对象的实例化逻辑,才会调用到构造函数和代码块,当全部枚举实例实例化完成后,类初始化完毕后便会执行静态代码块中的逻辑。


写在最后

这一点是在完成总结的时候想到的,因为上述方案只是在EnumA中进行了属性值的填充,但是它的前提是EnumA要优先在EnumB之前被调用,不然在EnumB实例化的时候会需要EnumA先实例化完成,但是填充逻辑是在EnumA中的,填充时又需要EnumB的实例,执行的话会报空指针。所以要保证枚举的加载顺序才行

解决方法

  • 第一步:将没有填充逻辑的枚举中构造函数中的入参中去掉类型为另外一个枚举的变量,这样做才可以保证正常的初始化。
  • 第二步:在没有填充逻辑的枚举中的静态代码块中调用一下有填充逻辑的枚举,这样就会触发实例化另外一个枚举的动作,并且执行填充逻辑。这样即使没有填充逻辑的枚举优先被调用,也可以获取到正常的值。

具体代码如下:

public enum EnumB {
    OPERATION_A("OPERATION_A", "操作1");

    private String code;
    private String desc;
    @Setter
    private EnumA  next;

    static {
       EnumA init = EnumA.STATUS_A;
    }

    EnumB(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容