枚举详解 - Thinking in Java

文章目录

一、枚举的本质

  • 在 java 中,经常使用 enum 来表示一组具名的值的有限集合。

  • 枚举的本质

    • 举例说明:

    ​ 像下面这个枚举类:

    package info.yingying.practice.test.enums;
    public enum TestEnum {
    
        HELLO_WORLD("helloWorld", "ok"),
        HI("hi", "ko");
    
        TestEnum(String s1, String s2) {
        }
        
    }
    

    ​ 在使用 javac TestEnum.java 编译之后,会得到一个字节码文件 TestEnum.class ,再利用 javap TestEnum.class 反编译字节码文件,效果如下:

    C:\Users\mayingying12\Desktop\马莹莹资料\IdeaProjects\practice\src\info\yingying\practice\test\enums>javap TestEnum.class
    Compiled from "TestEnum.java"
    public final class info.yingying.practice.test.enums.TestEnum extends java.lang.Enum<info.yingying.practice.test.enums.TestEnum> {
      public static final info.yingying.practice.test.enums.TestEnum HELLO_WORLD;
      public static final info.yingying.practice.test.enums.TestEnum HI;
      public static info.yingying.practice.test.enums.TestEnum[] values();
      public static info.yingying.practice.test.enums.TestEnum valueOf(java.lang.String);
      static {};
    }
    

    ​ 由于 javap 命令只可以看到 public 部分,并且并没有看到此类的构造方法,那会不会是因为构造函数被 javac 声明成 private 的了呢?

    ​ 我们再使用 javap -p TestEnum.class 查看私有方法和域,效果如下:

    C:\Users\mayingying12\Desktop\马莹莹资料\IdeaProjects\practice\src\info\yingying\practice\test\enums>javap -p TestEnum.class
    Compiled from "TestEnum.java"
    public final class info.yingying.practice.test.enums.TestEnum extends java.lang.Enum<info.yingying.practice.test.enums.TestEnum> {
      public static final info.yingying.practice.test.enums.TestEnum HELLO_WORLD;
      public static final info.yingying.practice.test.enums.TestEnum HI;
      private static final info.yingying.practice.test.enums.TestEnum[] $VALUES;
      public static info.yingying.practice.test.enums.TestEnum[] values();
      public static info.yingying.practice.test.enums.TestEnum valueOf(java.lang.String);
      private info.yingying.practice.test.enums.TestEnum(java.lang.String, java.lang.String);
      static {};
    }
    

    ​ 因此,我们可以总结出:

    ① 枚举实际上是 final class 的 java 类,这也符合枚举不可被继承的特点。
    ② 同时 Enum 继承了 java.lang.Enum 类,由于 java 是单继承的,这意味着枚举不可以继承其他类了。
    ③ 枚举中的值都被 final 修饰,以维护不可变性,并且是 public static 的,可以直接访问他们。
    ④ 枚举类的构造函数被声明为 private,这意味着用户不可以实例化枚举类。
    

    问题:

    ① static{} 是用来做什么的
    ② 值是什么时候被初始化的
    ③ values() 方法是用来做什么的
    

    ​ 举例:

    public enum Color {
        RED, GREEN, BLUE;
    }
    
    public final class Color extends Enum<Color> {
    
        public static final Color RED;
        public static final Color GREEN;
        public static final Color BLUE;
    
        // ---------- javac ----------
        private static final Color[] $VALUES;
    
        public static Color[] values() {
            return $VALUES.clone();
        }
        // ---------------------------
    
        // ---------- ECJ ----------
        // private static final Color[] ENUM$VALUES;
        //
        // public static Color[] values() {
        //     Color[] copy = new Color[ENUM$VALUES.length];
        //     System.arraycopy(ENUM$VALUES, 0, copy, 0, ENUM$VALUES.length);
        //     return copy;
        // }
        // -------------------------
    
        public static Color valueOf(String name) {
            return (Color) Enum.valueOf(Color.class, name);
        }
    
        private Color(String name, int ordinal) {
            super(name, ordinal);
        }
    
        static {
            RED = new Color("RED", 0);
            GREEN = new Color("GREEN", 1);
            BLUE = new Color("BLUE", 2);
            $VALUES = new Color[3];
            $VALUES[0] = RED;
            $VALUES[1] = GREEN;
            $VALUES[2] = BLUE;
        }
    
    }
    

    结论:

    ① 若未自动添加构造函数,编译时会自动生成 private 的构造函数,接收两个参数,一个是枚举对象的名字,一个是位置,在构造函数中直接调用了super(String, int),即java.lang.Enum的protected构造函数来构造对象。
    ② 编译时会自动添加 static 代码块儿,来初始化所有的枚举对象,并添加到自动生成的一个数组常量中储存起来, ECJ 编译的是 ENUM$VALUES,javac 编译的是 $VALUES。
    ③ 编译时会自动生成 public static 的 values() 方法-返回所有枚举对象的数组,也是在编译时期隐式添加的。
    ④ 编译时会自动生成 public static 的 valueOf(String) 方法,直接调用了 Enum.valueOf() 方法。
    

    注意:

    1. 枚举对象的构造和赋值先于所有的 static 代码
    2. 枚举类的初始化和枚举对象的初始化过程和普通的类完全不同。普通的类是先完成类的初始化,然后才构造对象,构造对象的时候所有的静态成员都已经准备好了;但是枚举不同,枚举类初始化的第一步就是要构造所有的枚举对象,也就是说,枚举对象构造时,静态成员还完全没有准备好,这也是为什么在枚举对象的构造函数中,引用非final的static成员会编译不通过。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值