小工具知识网

当前位置: 首页 > 行业知识 > Java | 少年,你真懂枚举吗?

行业知识

Java | 少年,你真懂枚举吗?

2026-10-26 14:53:34 来源:互联网转载

✍作者:周棋洛,大三计算机学生。

♉星座:金牛座

主页:我的个人网站

关键:Java 枚举 enum

目录

  • 引言
  • 灵魂发问?
    • 什么是枚举?
    • 什么是java枚举类?
    • java底层如何处理枚举类?
    • 枚举类为什么不允许new?
    • 枚举类构造器不允许用public,protected等修饰?
    • 枚举类构造器是私有的,怎么证明?
    • 反编译,研究透彻
    • 使用篇
      • 简单枚举
      • 带属性的枚举
      • 带有自定义方法的枚举
      • 带有复杂逻辑的枚举
      • 带有接口的枚举
      • end

        引言

        最近有读柏拉图的《理想国》,我不禁思考,要做一个看似xx的人,还是要做一个真正xx的人 。思考后,我想做一个真正xx的人,虽然可能做不到,但有句话鼓励着我,虽不能至,心向往之。

        Java5中添加了enum关键字,今天我们来巩固基础,学习Java枚举的相关知识。

        灵魂发问?

        学一个知识,我脑袋中不仅会有好多问号?比如:

        什么是枚举?

        枚举是一种数据类型,用于表示一组有限且固定的常量集合。枚举类型可以帮助开发人员编写更加清晰、可读性更高的代码,而不需要依赖于魔法数值或者字符串。

        什么是java枚举类?

        在Java中,枚举通过关键字enum定义,因此Java中的枚举类型也被称为枚举类(Enum class)。枚举类是一种特殊的类,用于表示枚举类型。

        枚举类中的每个枚举常量都是类的一个实例,并且这些实例在定义时就被确定了,不能在运行时修改。

        枚举类可以有方法,构造函数和字段,因此可以像普通类一样进行操作,但是枚举常量通常是不可变的。

        在Java中,枚举类通常用于替代常量集合或者表示状态机等场景,以提高代码的可读性和可维护性。枚举类的使用方式类似于普通类,但其语法更简洁明了,因此很多情况下都是一个不错的选择。

        java底层如何处理枚举类?

        枚举类在java中被编译为一个普通的类,但是编译器会自动为枚举类添加一些特殊的方法和属性,比如values()方法用于返回枚举常量数组,valueOf()方法用于将字符串转换为对应的枚举常量等。

        枚举类为什么不允许new?

        在Java中,枚举类型是一种特殊的类,它已经预先定义了一组有限的实例。编译器会自动添加一些特殊的处理,使得枚举常量在类加载时就被创建,并且在整个JVM中只存在一个实例。

        枚举常量的创建是在类加载时就完成的,而且在加载枚举类时,编译器会生成相应数量的枚举实例,这些实例是在静态代码块中进行初始化的(我们会在后续的反编译详细研究)。因此,枚举类在加载时就已经创建了所有可能的实例,不需要使用new关键字来实例化。

        另外,枚举类通常用于一组有限的常量集合,也就是我们不需要在运行时动态地创建新的实例。因此,禁用new关键字实例化可以确保枚举类型的单例性和稳定性。

        枚举类构造器不允许用public,protected等修饰?

        当你给枚举类构造器用public,protected修饰时会提示“此处不允许使用修饰符xxx”,它允许空修饰符或者private修饰符,虽所private修饰符会提示“修饰符 'private' 对于枚举构造函数是冗余的”,但不报错。

        为什么private时,提示冗余呢?因为当你定义一个枚举类时,编译器会自动将枚举常量的构造器设为私有的,这是为了防止在类外部创建新的枚举实例。所以,私有构造器可以保证枚举类型的单例性和不变性。

        枚举类构造器是私有的,怎么证明?

        枚举类的构造器是私有(private)的,怎么证明呢?

        枚举类HttpCodeEnum

        在枚举类中定义两个构造器。

        public enum HttpCodeEnum {    SUCCESS("成功", 200);    String msg;    int code;    HttpCodeEnum() {}    HttpCodeEnum(String msg, int code) {        this.msg = msg;        this.code = code;    }}

        其实可以通过反射在程序运行时,动态获取构造器的信息,代码如下:

        // 获取枚举类的构造方法Constructor[] declaredConstructors = HttpCodeEnum.class.getDeclaredConstructors();// 遍历构造方法for (Constructor constructor : declaredConstructors) {    // 输出构造方法的修饰符和名称    System.out.println("Constructor: " + constructor.getModifiers() + " " + constructor.getName());}

        执行上面代码,结果如下:

        Constructor: 2 com.zhouql.domain.entity.HttpCodeEnumConstructor: 2 com.zhouql.domain.entity.HttpCodeEnum

        有些小伙伴可能对反射不太了解,没关系看一下getModifiers源码:

        /** * Returns the Java language modifiers for the executable represented by this object. * 返回此对象表示的可执行文件的Java语言修饰符。 */@Overridepublic int getModifiers() {    return modifiers;}

        该方法的返回值是int,和修饰符的映射如下:

        0:可能代表没有任何修饰符修饰。

        1:可能代表被 public 修饰符修饰。

        2:可能代表被 private 修饰符修饰。

        …

        这就清楚了,这个类有两个构造器,都是私有(private)的。

        反编译,研究透彻

        遇事不决,反编译看看,创建一个空的枚举类,如下:

        /** * Date: 2024/5/15 14:18 * Author: 王子周棋洛 * Description: This is a newly created class. */public enum HttpCodeEnum {}

        反编译结果如下:

        public final class com.zhouql.domain.entity.HttpCodeEnum extends java.lang.Enum {  public static com.zhouql.domain.entity.HttpCodeEnum[] values();  public static com.zhouql.domain.entity.HttpCodeEnum valueOf(java.lang.String);  static {};}

        有点意思了,可以看到枚举类经过编译,其实就是一个Java类啊,只不过它稍微特殊些罢了,我们继续往下看:

        final class 关键字,意味着该类不允许被继承。表示枚举类是最终的,不可派生子类。

        extends Enum 继承自Enum类(java.lang)

        看看Enum类的构造器,英语不好,下面是机翻的doc:

        独家建造商。程序员无法调用此构造函数。它供编译器在响应枚举类型声明时发出的代码使用。

        形参:

        name–-此枚举常量的名称,它是用于声明它的标识符。

        ordinal–-此列举常量的序号(它在枚举声明中的位置,其中初始常量被分配了一个序号0)。

        protected Enum(String name, int ordinal) {    this.name = name;    this.ordinal = ordinal;}public final int ordinal() {    return ordinal;}public final String name() {return name;}

        大概意思是,这是个抽象类,我们无法实例化并手动调用。是提供给编译器在创建枚举类型时用的,形参name就是常量名,ordinal代表常量序号,说明多个常量顺序是有被记录的,有顺序。这也就是为什么我们的enum常量会有ordinal,name这些方法,是不是就合理了。

        HttpCodeEnum success = HttpCodeEnum.SUCCESS;System.out.println(success instanceof Enum);// true

        下面我们给枚举类增加些内容:

        public enum HttpCodeEnum {    SUCCESS("成功", 200);    String msg;    int code;    HttpCodeEnum(String msg, int code) {        this.msg = msg;        this.code = code;    }}

        再次反编译可以得到如下字节码:

        public final class com.zhouql.domain.entity.HttpCodeEnum extends java.lang.Enum {  public static final com.zhouql.domain.entity.HttpCodeEnum SUCCESS;  java.lang.String msg;  int code;  public static com.zhouql.domain.entity.HttpCodeEnum[] values();    Code:       0: getstatic     #1                  // Field $VALUES:[Lcom/zhouql/domain/entity/HttpCodeEnum;       3: invokevirtual #2                  // Method "[Lcom/zhouql/domain/entity/HttpCodeEnum;".clone:()Ljava/lang/Object;       6: checkcast     #3                  // class "[Lcom/zhouql/domain/entity/HttpCodeEnum;"       9: areturn  public static com.zhouql.domain.entity.HttpCodeEnum valueOf(java.lang.String);    Code:       0: ldc           #4                  // class com/zhouql/domain/entity/HttpCodeEnum       2: aload_0       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;       6: checkcast     #4                  // class com/zhouql/domain/entity/HttpCodeEnum       9: areturn  static {};    Code:       0: new           #4                  // class com/zhouql/domain/entity/HttpCodeEnum       3: dup       4: ldc           #9                  // String SUCCESS       6: iconst_0       7: ldc           #10                 // String 成功       9: sipush        200      12: invokespecial #11                 // Method "":(Ljava/lang/String;ILjava/lang/String;I)V      15: putstatic     #12                 // Field SUCCESS:Lcom/zhouql/domain/entity/HttpCodeEnum;      18: iconst_1      19: anewarray     #4                  // class com/zhouql/domain/entity/HttpCodeEnum      22: dup      23: iconst_0      24: getstatic     #12                 // Field SUCCESS:Lcom/zhouql/domain/entity/HttpCodeEnum;      27: aastore      28: putstatic     #1                  // Field $VALUES:[Lcom/zhouql/domain/entity/HttpCodeEnum;      31: return}

        有新发现,我们发现这次静态代码块有内容了。

        简单总结,他做了一下工作:

        • 创建了一个 HttpCodeEnum 实例并初始化(SUCCESS)。
        • 将这个实例存储在 HttpCodeEnum 类的静态字段 SUCCESS 中。
        • 创建了一个包含这个 SUCCESS 实例的数组。
        • 将这个数组存储在 HttpCodeEnum 类的静态字段 $VALUES 中。

          是不是清晰了,有种恍然大悟的感觉,哈哈。

          好了,就到这,不再深究了,下面我们来看看使用篇,怎么用呢?

          使用篇

          因为我本人Java代码写的不多,水平比较低。所以我追求想掌握它。

          这里参考下Enum的实现类,看看别人都是怎么用枚举类的。

          简单枚举

          java.nio.file

          public enum FileVisitResult {    CONTINUE,    TERMINATE,    SKIP_SUBTREE,    SKIP_SIBLINGS;}

          用法学习:

          1. 定义一个enum关键字声明的枚举类
          2. 把需要枚举的常量以大写的形式定义出来
          3. 如果只有一个直接分号;结束
          4. 如果有多个,用逗号隔开,最后用分号结束
          5. 枚举值命名,采用全大写,多个单词用下划线分割

          带属性的枚举

          HttpCodeEnum

          枚举类只是特殊的类,有时候单个枚举常量可能不足以满足我们描述它,因此我们可以提供字段,构造方法,并通过构造方法设置枚举值。

          public enum HttpCodeEnum {    OK(200, "Success"),    BAD_REQUEST(400, "Bad Request"),    NOT_FOUND(404, "Not Found");    private final int code;    private final String description;    HttpCodeEnum(int code, String description) {        this.code = code;        this.description = description;    }    public int getCode() {        return code;    }    public String getDescription() {        return description;    }}

          Test

          public static void main(String[] args) {    HttpCodeEnum status = HttpCodeEnum.OK;    System.out.println("Status code: " + status.getCode() + ", Description: " + status.getDescription());}// runStatus code: 200, Description: Success

          带有自定义方法的枚举

          Operation

          通过定义抽象方法,并在枚举常量提供实现。

          public enum Operation {    PLUS {        public double apply(double x, double y) { return x + y; }    },    MINUS {        public double apply(double x, double y) { return x - y; }    },    TIMES {        public double apply(double x, double y) { return x * y; }    },    DIVIDE {        public double apply(double x, double y) { return x / y; }    };    public abstract double apply(double x, double y);}

          Test

          public static void main(String[] args) {    double x = 20.0;    double y = 10.0;    for (Operation op : Operation.values()) {        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));    }}// run20.000000 PLUS 10.000000 = 30.00000020.000000 MINUS 10.000000 = 10.00000020.000000 TIMES 10.000000 = 200.00000020.000000 DIVIDE 10.000000 = 2.000000

          带有复杂逻辑的枚举

          比如我们用枚举实现交通信号灯控制:

          TrafficLight

          经过前面的探究,我们知道了每个枚举值都对应枚举类的一个实例,所以可以有构造,方法重写等能力。

          看看下面代码是不是就很巧妙。

          public enum TrafficLight {    RED(30) {        @Override        public TrafficLight next() {            return GREEN;        }    },    GREEN(45) {        @Override        public TrafficLight next() {            return YELLOW;        }    },    YELLOW(5) {        @Override        public TrafficLight next() {            return RED;        }    };    private final int duration; // in seconds    TrafficLight(int duration) {        this.duration = duration;    }    public int getDuration() {        return duration;    }    public abstract TrafficLight next();}

          Test

          public static void main(String[] args) {    TrafficLight currentLight = TrafficLight.RED;    for (int i = 0; i  

          带有接口的枚举

          /** * Date: 2024/5/16 19:32 * Author: 王子周棋洛 * Description: 定义接口 */interface Drawable {    void draw();}
          /** * Date: 2024/5/16 19:32 * Author: 王子周棋洛 * Description: 枚举实例实现接口实现draw方法 */ public enum Shape implements Drawable {    CIRCLE {        @Override        public void draw() {            System.out.println("Drawing a circle");        }    },    SQUARE {        @Override        public void draw() {            System.out.println("Drawing a square");        }    }}

          Test

          public static void main(String[] args) {    for (Shape shape : Shape.values()) {        shape.draw();    }}// runDrawing a circleDrawing a square

          end

          好啦,暂且学到这,下课。

上一篇:gea5怎么进服务器?

下一篇:Java Servlet源码深度解析,核心技术详解与实现探究