【Java Enum枚举】


Java Enum 笔记

很多人初学 Java 时,把枚举简单理解为"一个安全的常量集合"。这没错,但远远不够。枚举的本质是一个功能完备、特性丰富的类,用好了能让代码变得非常优雅和健壮。

1. 基础:替换 public static final

在枚举出现之前,我们用一组 public static final 的 int 或 String 来表示常量。

java 复制代码
// 旧方式
public static final int STATUS_PENDING = 0;
public static final int STATUS_APPROVED = 1;
public static final int STATUS_REJECTED = 2;

这种方式有几个致命弱点:

  • 类型不安全 :任何 int 值都能传入,编译器不会报错。setStatus(99) 完全合法。
  • 无语义:打印出来是数字,可读性差。
  • 命名空间污染 :常量名必须保证唯一。
    枚举优雅地解决了这些问题。
java 复制代码
// 新方式
public enum OrderStatus {
    PENDING,
    APPROVED,
    REJECTED
}
  • 类型安全setStatus(OrderStatus.PENDING) 只能接受 OrderStatus 的三个实例之一。
  • 语义清晰 :打印出来就是 PENDING,可读性极佳。
  • 自带命名空间 :通过 OrderStatus.PENDING 调用。
2. 本质:一个继承自 java.lang.Enum 的类

这是理解枚举的关键。编译器会将 enum 关键字转换成一个最终的类,这个类:

  1. 继承自 java.lang.Enum。因此,所有枚举都共享 Enum 的方法。
  2. final 的,无法被继承。
  3. 构造方法是 private 的,确保实例只能在类内部创建,且在类加载时就被实例化。
    这意味着,枚举可以拥有字段、方法、构造方法 ,甚至可以实现接口
java 复制代码
public enum Planet {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6);
    private final double mass;   //以千克为单位
    private final double radius; // 以米为单位
    // 构造器必须是 private
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    public double mass() { return mass; }
    public double radius() { return radius; }
    // 可以有自己的方法
    public double surfaceGravity() {
        final double G = 6.67300E-11; //万有引力常数
        return G * mass / (radius * radius);
    }
}

思考 :这种设计让枚举从一个"常量列表"变成了"一组相关的、行为各异的、有限的对象"。比如 Planet,每个行星都是一个对象,有自己的属性(质量、半径)和行为(计算表面重力)。这比用一堆静态变量和静态方法要面向对象得多。

3. 进阶:为每个枚举常量赋予不同的行为

这是枚举最强大的特性之一:可以为每个枚举常量提供其独有的方法实现,通过声明抽象方法来实现。

java 复制代码
public enum Operation {
    PLUS {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };
    // 抽象方法,强制每个常量必须实现
    public abstract double apply(double x, double y);
}

这里的 PLUS { ... } 语法,本质上相当于一个匿名内部类,它继承了 Operation 并实现了 apply 方法。
思考 :这种写法完美替代了 if-elseswitch 语句。当需要增加新的运算时,只需添加一个新的枚举常量并实现其逻辑,而无需修改原有的 if-elseswitch 结构。这完全符合开闭原则(对扩展开放,对修改关闭)。

4. 常用 API 与陷阱
  • name(): 返回枚举常量的名称,如 "PLUS"通常建议只在需要序列化或与外部系统交互时使用。
  • ordinal(): 返回枚举常量在声明中的序号(从0开始)。强烈不建议在业务逻辑中使用它! 它的值极易因为修改枚举声明的顺序而改变,非常脆弱。如果需要关联索引,自己加一个字段。
  • values(): 返回一个包含所有枚举常量的数组。这个方法是编译器加的,Enum 类本身没有。
  • valueOf(String): 根据名称字符串获取对应的枚举实例。如果名称不存在,会抛出 IllegalArgumentException
5. 思考与最佳实践
  1. 枚举是实现单例模式的最佳方式
    《Effective Java》中明确指出,单元素枚举是实现单例的最佳方法。因为它不仅能防止通过反射创建新对象,还能保证在序列化和反序列化时不会创建新对象。JVM 从根本上保证了单例的唯一性。

    java 复制代码
    public enum Singleton {
        INSTANCE;
    
        public void doSomething() {
            // ...
        }
    }
  2. 何时使用枚举?
    当你需要一组固定的、编译时确定的 常量时。例如:星期、月份、订单状态、操作系统类型等。
    如果这组值是运行时动态变化的(比如从数据库或配置文件读取),那么枚举就不适用了,应该考虑 MapList

  3. 枚举与 switch
    switch 语句天然支持枚举,并且编译器会进行检查,确保你没有遗漏任何一个 case(如果没有 default 分支)。这是一个很好的实践。

  4. 不要用 == 还是 .equals()
    对于枚举,用 == 就够了。JVM 保证每个枚举常量只有一个实例,所以 ==.equals() 效果相同,且 == 更高效、更简洁。

总结
  • 基础:类型安全的常量集合。
  • 进阶:一个功能完备的类,可以有自己的属性、方法。
  • 高阶 :可以为每个常量实现不同的行为,替代 if-else/switch
  • 最佳实践 :是实现单例模式的最佳选择,但不要滥用 ordinal()
    不要再把枚举只看作常量了。它是一个强大、安全且优雅的 Java 特性,善用它,你的代码会提升一个档次。

相关推荐
阿猿收手吧!1 小时前
【C++】cpp虚函数和纯虚函数的声明和定义
开发语言·c++
q_30238195561 小时前
Python实现基于多模态知识图谱的中医智能辅助诊疗系统:迈向智慧中医的新篇章
开发语言·python·知识图谱
爬山算法1 小时前
Redis(168) 如何使用Redis实现会话管理?
java·数据库·redis
程语有云1 小时前
生产事故-那些年遇到过的OOM
java·内存·oom·生产事故
雨中飘荡的记忆1 小时前
Spring Test详解
java·后端·spring
梨落秋霜1 小时前
Python入门篇【输入input】
开发语言·python
wen-pan1 小时前
Go 语言 GMP 调度模型深度解析
开发语言·go
Buxxxxxx1 小时前
DAY 34 模块和库的导入
开发语言·python
sugar__salt1 小时前
网络编程套接字(二)——TCP
java·网络·网络协议·tcp/ip·java-ee·javaee