Java基础(十四):枚举类详解

Java基础系列文章

Java基础(一):初识Java------发展历程、技术体系与JDK环境搭建

Java基础(二):八种基本数据类型详解

Java基础(三):逻辑运算符详解

Java基础(四):位运算符详解

Java基础(五):流程控制全解析------分支(if/switch)和循环(for/while)的深度指南

Java基础(六):数组全面解析

Java基础(七): 面向过程与面向对象、类与对象、成员变量与局部变量、值传递与引用传递、方法重载与方法重写

Java基础(八):封装、继承、多态与关键字this、super详解

Java基础(九):Object核心类深度剖析

Java基础(十):关键字static详解

Java基础(十一):关键字final详解

Java基础(十二):抽象类与接口详解

Java基础(十三):内部类详解

Java基础(十四):枚举类详解

目录

一、枚举类概述

枚举(Enum)是Java 5引入的一种特殊数据类型,它允许我们预定义一组常量。在Java中,枚举是一种特殊的类,它继承自java.lang.Enum类,具有类的所有特性。

为什么需要枚举?

  • 在枚举出现之前,我们通常使用以下方式表示一组常量
java 复制代码
public class Season {
    public static final int SPRING = 1;
    public static final int SUMMER = 2;
    public static final int AUTUMN = 3;
    public static final int WINTER = 4;
}

这种方式存在几个问题:

  1. 类型系统约束​:枚举变量只能接受特定的枚举常量,传统常量编译时不能校验合法性
  2. 可读性差​:数字本身没有表达其含义,难以理解
  3. 无法添加属性或方法
  4. 难以维护​:如果常量值需要修改,可能影响多处代码

枚举解决了这些问题,提供了更安全、更强大的常量表示方式。

二、基本枚举定义

1、最简单的枚举

  • 这个枚举定义了7个常量,每个都是Day类型的实例
java 复制代码
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

编译器生成的大致等价代码:

java 复制代码
// 编译器生成的类,实际名称仍然是 Day,继承自 java.lang.Enum
public final class Day extends Enum<Day> {
    // 枚举实例,是 Day 类的静态 final 实例,每个都是唯一的
    public static final Day SUNDAY = new Day("SUNDAY", 0);
    public static final Day MONDAY = new Day("MONDAY", 1);
    public static final Day TUESDAY = new Day("TUESDAY", 2);
    public static final Day WEDNESDAY = new Day("WEDNESDAY", 3);
    public static final Day THURSDAY = new Day("THURSDAY", 4);
    public static final Day FRIDAY = new Day("FRIDAY", 5);
    public static final Day SATURDAY = new Day("SATURDAY", 6);

    // 内部维护一个所有枚举值的数组,可通过 Day.values() 获取
    private static final Day[] $VALUES = new Day[]{
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
    };

    // 枚举的构造方法是私有的,不允许外部创建新的实例
    private Day(String name, int ordinal) {
        super(name, ordinal); // 调用父类 Enum 的构造方法
    }

    // 返回所有枚举值,等同于你调用 Day.values()
    public static Day[] values() {
        return $VALUES.clone(); // 返回一个副本,防止外部修改内部数组
    }

    // 根据名称返回对应的枚举实例,等同于你调用 Day.valueOf("MONDAY")
    public static Day valueOf(String name) {
        return Enum.valueOf(Day.class, name); // 调用 Enum 类的静态方法
    }
}

2、带有属性和方法的枚举

  • ​实例声明​:枚举实例是类级别的,必须在首行用逗号(,)分隔,若枚举类包含方法/属性,最后一个实例后需加分号(;)
  • 构造函数​:枚举的构造函数默认且只能是private​(即使不写修饰符),防止外部创建新实例
  • 属性限制​:属性通常声明为final(不强求),保证不可变性(枚举实例通常是单例且线程安全的)
java 复制代码
// 订单状态枚举
public enum OrderStatus {
    // 枚举实例(必须位于首行)
    PENDING("待支付", 0),
    PAID("已支付", 1),
    SHIPPED("已发货", 2),
    COMPLETED("已完成", 3);

    // 枚举属性(通常为final)
    private final String desc;  // 描述
    private final int code;     // 状态码

    // 构造函数(必须私有!,没有修饰符也表示private)
    private OrderStatus(String desc, int code) {
        this.desc = desc;
        this.code = code;
    }

    // 根据 code状态码 直接获取对应的 desc描述
    private static final Map<Integer, OrderStatus> CODE_TO_ENUM_MAP = new HashMap<>();
    // 静态代码块:初始化映射
    static {
        for (OrderStatus status : OrderStatus.values()) {
            CODE_TO_ENUM_MAP.put(status.getCode(), status);
        }
    }
    public static String getDescByCode(int code) {
        OrderStatus status = CODE_TO_ENUM_MAP.get(code);
        if (status == null){
            throw new IllegalArgumentException("无效的订单状态码: " + code);
        }
        return status.getDesc();
    }

    // Getter方法
    public String getDesc() {
        return desc;
    }
    public int getCode() {
        return code;
    }
}
  • 枚举实例本质是类的静态常量,通过类名直接访问
java 复制代码
OrderStatus status = OrderStatus.PAID;
System.out.println(status.getDesc());  // 输出:已支付
System.out.println(status.getCode());  // 输出:1

编译器生成的大致等价代码:

java 复制代码
public final class OrderStatus extends Enum<OrderStatus> {
    // 静态的枚举实例,每个都是 OrderStatus 类型的静态 final 对象
    public static final OrderStatus PENDING = new OrderStatus("PENDING", 0, "待支付", 0);
    public static final OrderStatus PAID = new OrderStatus("PAID", 1, "已支付", 1);
    public static final OrderStatus SHIPPED = new OrderStatus("SHIPPED", 2, "已发货", 2);
    public static final OrderStatus COMPLETED = new OrderStatus("COMPLETED", 3, "已完成", 3);
    
    // // 编译器生成的 values 数组,用于 values() 方法(按声明顺序)
    private static final OrderStatus[] $VALUES = {
        PENDING, PAID, SHIPPED, COMPLETED
    };
    
    // 自定义字段
    private final String desc;
    private final int code;
    
    // 私有构造器,确保外部无法随意创建枚举实例
    private OrderStatus(String name, int ordinal, String desc, int code) {
        super(name, ordinal);  // 调用父类 Enum 的构造方法
        this.desc = desc;
        this.code = code;
    }
    
    // 自定义的code获取desc方法
    private static final Map<Integer, OrderStatus> CODE_TO_ENUM_MAP = new HashMap<>();
    static {
        for (OrderStatus status : OrderStatus.values()) {
            CODE_TO_ENUM_MAP.put(status.getCode(), status);
        }
    }
    public static String getDescByCode(int code) {
        OrderStatus status = CODE_TO_ENUM_MAP.get(code);
        if (status == null){
            throw new IllegalArgumentException("无效的订单状态码: " + code);
        }
        return status.getDesc();
    }
    
    // 自定义的getter方法
    public String getDesc() {
        return desc;
    }
    public int getCode() {
        return code;
    }


    // 编译器生成的方法
    public static OrderStatus[] values() {
        return $VALUES.clone();// 返回所有枚举值的拷贝
    }
    public static OrderStatus valueOf(String name) {
    	// 根据 name 查找对应的枚举实例,找不到则抛出 IllegalArgumentException
        return Enum.valueOf(OrderStatus.class, name);
    }
}

3、枚举的特点

  1. 枚举常量默认是public static final
  2. 枚举类隐式继承自java.lang.Enum
  3. 枚举不能被继承(因为已经继承了Enum
  4. 枚举构造函数总是私有的​(可以省略private关键字)
  5. 枚举可以添加字段方法构造函数

三、枚举方法和工具类

1、枚举的隐含方法和属性

所有枚举都隐式继承自java.lang.Enum,因此具有以下方法:

  1. name():返回枚举常量的名称(字符串)
  2. ordinal():返回枚举常量的序数(声明顺序,从0开始)
  3. toString():默认返回 name,可重写提供友好信息
  4. equals():比较两个枚举引用是否指向同一个对象(但通常直接用==更直观)
  5. hashCode():枚举的哈希码实现( 一般不用关心)
  6. compareTo(E o):比较两个枚举常量的序数(一般不用,除非需要排序)
  7. valueOf(String name):通过name字符串获取枚举常量(由编译器生成,不是Enum类的方法)
  8. values():返回枚举的所有常量(也是由编译器生成,不是Enum类的方法)

示例:

java 复制代码
enum Color { RED, GREEN, BLUE }

public class EnumDemo {
    public static void main(String[] args) {
        Color c = Color.RED;
        
        System.out.println(c.name());     // 输出: RED
        System.out.println(c.ordinal());  // 输出: 0
        System.out.println(c.toString()); // 输出: RED
        
        // 使用values()方法
        for (Color color : Color.values()) {
            System.out.println(color);
        }
        
        // 使用valueOf()方法
        Color red = Color.valueOf("RED");
        System.out.println(red == Color.RED); // 输出: true
    }
}

2、工具类EnumSet/EnumMap

Java提供了专门用于枚举的集合实现,位于java.util包中:

  1. EnumSet - 高性能的枚举集合实现
  2. EnumMap - 键为枚举的高性能Map实现

EnumSet示例:

java 复制代码
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class EnumSetDemo {
    public static void main(String[] args) {
        // 创建一个包含所有Day枚举的EnumSet
        EnumSet<Day> allDays = EnumSet.allOf(Day.class);
        System.out.println("所有天: " + allDays);
        
        // 创建一个空的EnumSet
        EnumSet<Day> noDays = EnumSet.noneOf(Day.class);
        noDays.add(Day.MONDAY);
        noDays.add(Day.FRIDAY);
        System.out.println("工作日: " + noDays);
        
        // 创建一个包含范围的EnumSet
        EnumSet<Day> weekend = EnumSet.range(Day.SATURDAY, Day.SUNDAY);
        System.out.println("周末: " + weekend);
        
        // 创建一个补集
        EnumSet<Day> weekdays = EnumSet.complementOf(weekend);
        System.out.println("工作日: " + weekdays);
    }
}

EnumMap示例:

java 复制代码
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class EnumMapDemo {
    public static void main(String[] args) {
        EnumMap<Day, String> activityMap = new EnumMap<>(Day.class);
        
        activityMap.put(Day.MONDAY, "上班");
        activityMap.put(Day.TUESDAY, "上班");
        activityMap.put(Day.WEDNESDAY, "上班");
        activityMap.put(Day.THURSDAY, "上班");
        activityMap.put(Day.FRIDAY, "上班");
        activityMap.put(Day.SATURDAY, "休息");
        activityMap.put(Day.SUNDAY, "休息");
        
        for (Day day : Day.values()) {
            System.out.println(day + ": " + activityMap.get(day));
        }
    }
}

四、枚举实现接口

枚举类可以实现一个或多个接口,为所有实例或单个实例提供统一行为。这在需要为不同枚举实例定义差异化逻辑时非常有用。

示例:定义支付策略接口:

java 复制代码
// 支付策略接口
public interface PaymentStrategy {
    void pay(double amount);
}

// 支付方式枚举(实现接口)
public enum PaymentMethod implements PaymentStrategy {
    ALIPAY {
        @Override
        public void pay(double amount) {
            System.out.println("使用支付宝支付:" + amount + "元");
        }
    },
    WECHAT_PAY {
        @Override
        public void pay(double amount) {
            System.out.println("使用微信支付:" + amount + "元");
        }
    },
    BANK_CARD {
        @Override
        public void pay(double amount) {
            System.out.println("使用银行卡支付:" + amount + "元");
        }
    };
}

使用接口方法:

java 复制代码
PaymentMethod method = PaymentMethod.ALIPAY;
method.pay(199.9);  // 输出:使用支付宝支付:199.9元

五、枚举的单例模式

单例模式的核心是确保类仅一个实例,并提供全局访问点。枚举凭借JVM的类加载机制,天生具备线程安全、防反射攻击、防反序列化漏洞的特性,是《Effective Java》作者Joshua Bloch推荐的最佳实现方式。

1、为什么使用枚举实现单例?

  • 线程安全​:枚举实例的创建是由JVM在类加载时完成的,保证线程安全
  • 防止反射攻击​:普通的单例如果通过反射调用私有构造方法,可能会创建多个实例,但枚举类型不允许通过反射创建实例
    • Java 语言规范禁止反射创建枚举实例​,否则抛异常Cannot reflectively create enum objects
  • 避免序列化问题​:普通的单例如果实现了Serializable接口,在反序列化时可能会生成新的对象。而枚举天然防止了这一问题
    • 因为普通单例反序列化时,会通过反射或其他方式创建一个新的对象实例,而不是返回原来的那个单例对象
    • 而枚举序列化只保存枚举常量名称,不保存对象本身,反序列化时,JVM 根据这个名字直接返回对应的、已经存在的枚举常量对象

示例:使用枚举实现单例模式

java 复制代码
public enum Singleton {
    INSTANCE; // 这就是单例对象

    // 可以添加单例的其他成员变量和方法
    private int value;

    public void doSomething() {
        System.out.println("Singleton is doing something. Value = " + value);
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

使用方法:

java 复制代码
public class Main {
    public static void main(String[] args) {
        // 获取单例对象
        Singleton singleton = Singleton.INSTANCE;

        singleton.setValue(42);
        singleton.doSomething(); // 输出: Singleton is doing something. Value = 42

        // 再次获取,仍然是同一个对象
        Singleton anotherSingleton = Singleton.INSTANCE;
        System.out.println(singleton == anotherSingleton); // 输出: true
    }
}

2、对比传统单例写法(如双重检查锁、静态内部类等)

实现方式 线程安全 防止反射攻击 防止序列化破坏单例 代码复杂度
枚举(推荐)
双重检查锁 (需额外处理) ⭐⭐⭐
静态内部类 (需额外处理) ⭐⭐
饿汉式 (需额外处理)
懒汉式(非线程安全)

不需要兼容老代码,且追求最安全、最简洁的单例实现,那么使用枚举是最佳选择!

相关推荐
zzzzls~16 小时前
Python 工程化: 用 Copier 打造“自我进化“的项目脚手架
开发语言·python·copier
言慢行善17 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
韶博雅17 小时前
emcc24ai
开发语言·数据库·python
专吃海绵宝宝菠萝屋的派大星17 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟17 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z17 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可17 小时前
Java 中的实现类是什么
java·开发语言
He少年17 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
AI_Claude_code17 小时前
ZLibrary访问困境方案四:利用Cloudflare Workers等边缘计算实现访问
javascript·人工智能·爬虫·python·网络爬虫·边缘计算·爬山算法
克里斯蒂亚诺更新17 小时前
myeclipse的pojie
java·ide·myeclipse