📊 Java 枚举类(Enum)完全指南
📖 枚举的基本概念
什么是枚举?
枚举是一种特殊的类,用于定义固定数量的常量。
为什么需要枚举?
// ❌ 传统方式:使用常量(问题多)
public class OldWay {
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
// ... 容易出错,类型不安全
public void setDay(int day) {
// 可能传入非法值:100, -1
}
}
// ✅ 枚举方式(类型安全)
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
FRIDAY, SATURDAY, SUNDAY
}
🎯 枚举的基本语法
1. 最简单的枚举
// 定义枚举
enum Color {
RED, // 枚举常量
GREEN, // 枚举常量
BLUE // 枚举常量
}
// 使用枚举
public class EnumBasic {
public static void main(String[] args) {
// 1. 声明枚举变量
Color myColor = Color.RED;
// 2. 比较枚举
if (myColor == Color.RED) {
System.out.println("颜色是红色");
}
// 3. switch 语句(完美配合)
switch (myColor) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
// 4. 遍历所有枚举值
System.out.println("\n所有颜色:");
for (Color color : Color.values()) {
System.out.println(color);
}
// 5. 获取枚举名称和序号
System.out.println("\n枚举详细信息:");
System.out.println("名称: " + myColor.name());
System.out.println("序号: " + myColor.ordinal());
System.out.println("字符串转枚举: " + Color.valueOf("RED"));
}
}
2. 带属性的枚举
// 枚举可以有自己的属性和方法
enum Planet {
// 枚举常量(调用构造器)
MERCURY("水星", 2439.7, 3.7),
VENUS("金星", 6051.8, 8.87),
EARTH("地球", 6371.0, 9.8),
MARS("火星", 3389.5, 3.7),
JUPITER("木星", 69911.0, 24.8),
SATURN("土星", 58232.0, 10.4),
URANUS("天王星", 25362.0, 8.7),
NEPTUNE("海王星", 24622.0, 11.2);
// 枚举属性
private final String chineseName;
private final double radius; // 半径(km)
private final double gravity; // 重力(m/s²)
// 枚举构造器(必须是private,可省略)
private Planet(String chineseName, double radius, double gravity) {
this.chineseName = chineseName;
this.radius = radius;
this.gravity = gravity;
}
// 枚举方法
public double getSurfaceArea() {
return 4 * Math.PI * radius * radius;
}
public double getWeight(double massOnEarth) {
return massOnEarth * gravity / 9.8;
}
// Getter 方法
public String getChineseName() {
return chineseName;
}
public double getRadius() {
return radius;
}
public double getGravity() {
return gravity;
}
// 查找方法
public static Planet findByChineseName(String name) {
for (Planet planet : values()) {
if (planet.chineseName.equals(name)) {
return planet;
}
}
return null;
}
}
public class PlanetDemo {
public static void main(String[] args) {
// 使用枚举
Planet earth = Planet.EARTH;
System.out.println("=== 行星信息 ===");
System.out.println("名称: " + earth.getChineseName());
System.out.println("半径: " + earth.getRadius() + " km");
System.out.println("表面积: " + earth.getSurfaceArea() + " km²");
System.out.println("重力: " + earth.getGravity() + " m/s²");
// 计算在地球上的重量
double myMass = 70; // 70kg
System.out.println("\n在不同行星上的重量:");
for (Planet planet : Planet.values()) {
double weight = planet.getWeight(myMass);
System.out.printf("%s: %.1f kg%n", planet.getChineseName(), weight);
}
// 查找行星
Planet found = Planet.findByChineseName("火星");
System.out.println("\n查找到的行星: " + found);
}
}
3. 带方法的枚举
// 枚举可以实现接口,定义抽象方法
enum Operation {
// 每个枚举常量实现抽象方法
ADD {
@Override
public double apply(double x, double y) {
return x + y;
}
@Override
public String getSymbol() {
return "+";
}
},
SUBTRACT {
@Override
public double apply(double x, double y) {
return x - y;
}
@Override
public String getSymbol() {
return "-";
}
},
MULTIPLY {
@Override
public double apply(double x, double y) {
return x * y;
}
@Override
public String getSymbol() {
return "*";
}
},
DIVIDE {
@Override
public double apply(double x, double y) {
if (y == 0) {
throw new ArithmeticException("除数不能为零");
}
return x / y;
}
@Override
public String getSymbol() {
return "/";
}
};
// 抽象方法(每个枚举常量必须实现)
public abstract double apply(double x, double y);
public abstract String getSymbol();
// 通用方法
public void calculateAndPrint(double x, double y) {
try {
double result = apply(x, y);
System.out.printf("%.2f %s %.2f = %.2f%n", x, getSymbol(), y, result);
} catch (ArithmeticException e) {
System.out.println("计算错误: " + e.getMessage());
}
}
}
public class OperationDemo {
public static void main(String[] args) {
System.out.println("=== 计算器 ===");
double a = 10.5;
double b = 2.5;
// 使用枚举进行计算
for (Operation op : Operation.values()) {
op.calculateAndPrint(a, b);
}
// 处理除零错误
System.out.println("\n测试除零:");
Operation.DIVIDE.calculateAndPrint(10, 0);
// 实际应用:命令模式
System.out.println("\n实际应用:");
Operation selected = Operation.MULTIPLY;
selected.calculateAndPrint(8, 7);
}
}
🏗️ 枚举的高级特性
1. 枚举实现接口
// 定义接口
interface Describeable {
String getDescription();
String getCategory();
}
// 枚举实现接口
enum HttpStatus implements Describeable {
// 枚举常量
OK(200, "请求成功"),
CREATED(201, "资源创建成功"),
BAD_REQUEST(400, "客户端错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "资源未找到"),
INTERNAL_ERROR(500, "服务器内部错误");
private final int code;
private final String description;
HttpStatus(int code, String description) {
this.code = code;
this.description = description;
}
// 实现接口方法
@Override
public String getDescription() {
return description;
}
@Override
public String getCategory() {
if (code >= 200 && code < 300) {
return "成功";
} else if (code >= 400 && code < 500) {
return "客户端错误";
} else if (code >= 500) {
return "服务器错误";
}
return "其他";
}
// 其他方法
public int getCode() {
return code;
}
public boolean isSuccess() {
return code >= 200 && code < 300;
}
public boolean isError() {
return code >= 400;
}
// 根据状态码查找
public static HttpStatus fromCode(int code) {
for (HttpStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("无效的状态码: " + code);
}
}
public class HttpStatusDemo {
public static void main(String[] args) {
System.out.println("=== HTTP 状态码 ===");
// 遍历所有状态码
for (HttpStatus status : HttpStatus.values()) {
System.out.printf("状态码: %d, 描述: %s, 分类: %s, 成功: %s%n",
status.getCode(),
status.getDescription(),
status.getCategory(),
status.isSuccess());
}
// 使用状态码
HttpStatus response = HttpStatus.OK;
System.out.println("\n当前响应: " + response.getDescription());
// 根据状态码查找
try {
HttpStatus found = HttpStatus.fromCode(404);
System.out.println("查找到: " + found.getDescription());
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
2. 枚举中的抽象方法
// 使用抽象方法让每个枚举常量有自己的行为
enum LoggerLevel {
DEBUG {
@Override
public void log(String message) {
System.out.println("[DEBUG] " + message);
}
@Override
public boolean shouldLog(LoggerLevel currentLevel) {
// DEBUG级别记录所有日志
return true;
}
},
INFO {
@Override
public void log(String message) {
System.out.println("[INFO] " + message);
}
@Override
public boolean shouldLog(LoggerLevel currentLevel) {
// INFO级别记录INFO及以上
return currentLevel.ordinal() >= INFO.ordinal();
}
},
WARN {
@Override
public void log(String message) {
System.out.println("[WARN] " + message);
}
@Override
public boolean shouldLog(LoggerLevel currentLevel) {
// WARN级别记录WARN及以上
return currentLevel.ordinal() >= WARN.ordinal();
}
},
ERROR {
@Override
public void log(String message) {
System.out.println("[ERROR] " + message);
}
@Override
public boolean shouldLog(LoggerLevel currentLevel) {
// ERROR级别只记录ERROR
return currentLevel == ERROR;
}
};
// 抽象方法
public abstract void log(String message);
public abstract boolean shouldLog(LoggerLevel currentLevel);
// 日志记录器
private static LoggerLevel currentLevel = INFO;
public static void setLevel(LoggerLevel level) {
currentLevel = level;
}
public static void log(LoggerLevel level, String message) {
if (level.shouldLog(currentLevel)) {
level.log(message);
}
}
// 便捷方法
public static void debug(String message) {
log(DEBUG, message);
}
public static void info(String message) {
log(INFO, message);
}
public static void warn(String message) {
log(WARN, message);
}
public static void error(String message) {
log(ERROR, message);
}
}
public class LoggerDemo {
public static void main(String[] args) {
System.out.println("=== 日志系统演示 ===");
// 设置日志级别
System.out.println("\n1. 设置为DEBUG级别(记录所有):");
LoggerLevel.setLevel(LoggerLevel.DEBUG);
LoggerLevel.debug("这是调试信息");
LoggerLevel.info("这是一般信息");
LoggerLevel.warn("这是警告信息");
LoggerLevel.error("这是错误信息");
System.out.println("\n2. 设置为WARN级别(只记录WARN和ERROR):");
LoggerLevel.setLevel(LoggerLevel.WARN);
LoggerLevel.debug("这条不会被记录");
LoggerLevel.info("这条也不会被记录");
LoggerLevel.warn("警告信息被记录");
LoggerLevel.error("错误信息被记录");
System.out.println("\n3. 直接使用枚举:");
LoggerLevel.DEBUG.log("直接调用DEBUG");
LoggerLevel.ERROR.log("直接调用ERROR");
}
}
3. 枚举单例模式(最佳实践)
/**
* 枚举实现单例模式 - 最佳方式
* 优点:
* 1. 线程安全
* 2. 防止反射攻击
* 3. 防止反序列化创建新实例
* 4. 代码简洁
*/
enum DatabaseConnection {
// 单例实例
INSTANCE;
private Connection connection;
// 枚举构造器自动private
DatabaseConnection() {
System.out.println("数据库连接初始化");
// 实际项目中这里会创建真实的数据库连接
this.connection = createConnection();
}
private Connection createConnection() {
// 创建数据库连接
// 返回模拟连接
return new Connection();
}
public Connection getConnection() {
return connection;
}
public void executeQuery(String sql) {
System.out.println("执行SQL: " + sql);
// 执行查询逻辑
}
// 模拟连接类
static class Connection {
public void close() {
System.out.println("关闭连接");
}
}
}
public class EnumSingletonDemo {
public static void main(String[] args) {
System.out.println("=== 枚举单例模式 ===");
// 获取单例实例
DatabaseConnection instance1 = DatabaseConnection.INSTANCE;
DatabaseConnection instance2 = DatabaseConnection.INSTANCE;
// 验证单例
System.out.println("instance1 == instance2: " + (instance1 == instance2));
// 使用单例
instance1.executeQuery("SELECT * FROM users");
// 防止反射攻击测试
System.out.println("\n尝试反射攻击:");
try {
// 枚举不能通过反射创建实例
java.lang.reflect.Constructor<DatabaseConnection> constructor =
DatabaseConnection.class.getDeclaredConstructor();
constructor.setAccessible(true);
DatabaseConnection illegal = constructor.newInstance();
} catch (Exception e) {
System.out.println("反射攻击失败: " + e.getMessage());
}
}
}
🔧 枚举的实用技巧
1. 枚举与switch的完美配合
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public boolean isWeekday() {
switch (this) {
case SATURDAY:
case SUNDAY:
return false;
default:
return true;
}
}
public String getWorkMessage() {
switch (this) {
case MONDAY:
return "周一综合症";
case FRIDAY:
return "感谢上帝,今天是星期五";
case SATURDAY:
case SUNDAY:
return "周末愉快!";
default:
return "努力工作";
}
}
public Day nextDay() {
switch (this) {
case MONDAY: return TUESDAY;
case TUESDAY: return WEDNESDAY;
case WEDNESDAY: return THURSDAY;
case THURSDAY: return FRIDAY;
case FRIDAY: return SATURDAY;
case SATURDAY: return SUNDAY;
case SUNDAY: return MONDAY;
default: throw new IllegalStateException();
}
}
}
public class DayDemo {
public static void main(String[] args) {
Day today = Day.MONDAY;
System.out.println("=== 工作日判断 ===");
for (Day day : Day.values()) {
System.out.printf("%s: %s, %s%n",
day,
day.isWeekday() ? "工作日" : "休息日",
day.getWorkMessage());
}
System.out.println("\n=== 下一天测试 ===");
Day current = Day.FRIDAY;
System.out.println("今天是: " + current);
System.out.println("明天是: " + current.nextDay());
System.out.println("后天是: " + current.nextDay().nextDay());
}
}
2. 枚举作为状态机
// 订单状态机
enum OrderStatus {
// 定义所有状态
CREATED {
@Override
public OrderStatus next() {
return PAID;
}
@Override
public boolean canCancel() {
return true;
}
},
PAID {
@Override
public OrderStatus next() {
return SHIPPED;
}
@Override
public boolean canCancel() {
return true;
}
},
SHIPPED {
@Override
public OrderStatus next() {
return DELIVERED;
}
@Override
public boolean canCancel() {
return false; // 已发货不能取消
}
},
DELIVERED {
@Override
public OrderStatus next() {
return COMPLETED;
}
@Override
public boolean canCancel() {
return false;
}
},
COMPLETED {
@Override
public OrderStatus next() {
return this; // 已完成是最终状态
}
@Override
public boolean canCancel() {
return false;
}
},
CANCELLED {
@Override
public OrderStatus next() {
return this; // 已取消是最终状态
}
@Override
public boolean canCancel() {
return false;
}
};
// 抽象方法:下一个状态
public abstract OrderStatus next();
// 抽象方法:是否可以取消
public abstract boolean canCancel();
// 状态转移方法
public OrderStatus transition() {
return next();
}
// 取消订单
public OrderStatus cancel() {
if (canCancel()) {
return CANCELLED;
}
throw new IllegalStateException("当前状态不能取消订单: " + this);
}
}
// 订单类
class Order {
private String orderId;
private OrderStatus status;
public Order(String orderId) {
this.orderId = orderId;
this.status = OrderStatus.CREATED;
}
public void pay() {
status = status.transition();
System.out.println("订单已支付,新状态: " + status);
}
public void ship() {
status = status.transition();
System.out.println("订单已发货,新状态: " + status);
}
public void deliver() {
status = status.transition();
System.out.println("订单已送达,新状态: " + status);
}
public void complete() {
status = status.transition();
System.out.println("订单已完成,新状态: " + status);
}
public void cancel() {
try {
status = status.cancel();
System.out.println("订单已取消,新状态: " + status);
} catch (IllegalStateException e) {
System.out.println("取消失败: " + e.getMessage());
}
}
public OrderStatus getStatus() {
return status;
}
public void printStatus() {
System.out.printf("订单 %s 状态: %s%n", orderId, status);
}
}
public class OrderStateMachine {
public static void main(String[] args) {
System.out.println("=== 订单状态机演示 ===");
Order order = new Order("20230001");
order.printStatus();
// 正常流程
System.out.println("\n正常流程:");
order.pay();
order.ship();
order.deliver();
order.complete();
// 创建新订单测试取消
System.out.println("\n测试取消订单:");
Order order2 = new Order("20230002");
order2.printStatus();
order2.pay();
order2.printStatus();
order2.cancel(); // 可以取消
order2.printStatus();
// 尝试取消已发货的订单
System.out.println("\n测试取消已发货订单:");
Order order3 = new Order("20230003");
order3.pay();
order3.ship();
order3.cancel(); // 应该失败
}
}
3. 枚举的策略模式
// 策略接口
interface DiscountStrategy {
double applyDiscount(double price);
}
// 使用枚举实现策略模式
enum DiscountType implements DiscountStrategy {
// 不同的折扣策略
NO_DISCOUNT {
@Override
public double applyDiscount(double price) {
return price;
}
},
STUDENT {
@Override
public double applyDiscount(double price) {
return price * 0.8; // 学生8折
}
},
MEMBER {
@Override
public double applyDiscount(double price) {
return price * 0.9; // 会员9折
}
},
HOLIDAY {
@Override
public double applyDiscount(double price) {
return price * 0.85; // 节日85折
}
},
VIP {
@Override
public double applyDiscount(double price) {
return price * 0.7; // VIP 7折
}
};
}
// 商品类
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public double calculatePrice(DiscountType discountType) {
double discountedPrice = discountType.applyDiscount(price);
System.out.printf("%s 原价: %.2f, %s折后: %.2f%n",
name, price, discountType.name(), discountedPrice);
return discountedPrice;
}
}
public class DiscountDemo {
public static void main(String[] args) {
System.out.println("=== 折扣策略演示 ===");
Product laptop = new Product("笔记本电脑", 5000.0);
Product phone = new Product("智能手机", 3000.0);
// 应用不同的折扣策略
System.out.println("\n笔记本电脑:");
laptop.calculatePrice(DiscountType.NO_DISCOUNT);
laptop.calculatePrice(DiscountType.STUDENT);
laptop.calculatePrice(DiscountType.VIP);
System.out.println("\n智能手机:");
phone.calculatePrice(DiscountType.MEMBER);
phone.calculatePrice(DiscountType.HOLIDAY);
// 批量处理
System.out.println("\n批量计算:");
Product[] products = {laptop, phone};
DiscountType[] discounts = DiscountType.values();
for (Product product : products) {
for (DiscountType discount : discounts) {
product.calculatePrice(discount);
}
System.out.println();
}
}
}
📊 枚举常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
| values() | 返回所有枚举值数组 | Color.values() |
| valueOf() | 根据名称返回枚举 | Color.valueOf("RED") |
| name() | 返回枚举名称 | Color.RED.name() |
| ordinal() | 返回枚举序号 | Color.RED.ordinal() |
| compareTo() | 比较枚举顺序 | RED.compareTo(GREEN) |
| toString() | 返回枚举名称(可重写) | Color.RED.toString() |
💡 枚举最佳实践
1. 何时使用枚举?
// ✅ 适合使用枚举的场景:
// 1. 有限的状态集合
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED }
// 2. 错误代码
enum ErrorCode {
SUCCESS(0, "成功"),
NOT_FOUND(404, "未找到"),
UNAUTHORIZED(401, "未授权");
}
// 3. 配置选项
enum LogLevel { DEBUG, INFO, WARN, ERROR }
// 4. 单例模式
enum Singleton { INSTANCE }
// 5. 策略模式
enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE }
2. 枚举设计建议
// 好的枚举设计
public enum GoodEnum {
// 1. 常量名全大写
CONSTANT_ONE("描述1"),
CONSTANT_TWO("描述2");
// 2. 私有final字段
private final String description;
// 3. 私有构造器
private GoodEnum(String description) {
this.description = description;
}
// 4. Getter方法
public String getDescription() {
return description;
}
// 5. 业务方法
public boolean isValid() {
return this != CONSTANT_TWO;
}
// 6. 静态工具方法
public static GoodEnum fromDescription(String desc) {
for (GoodEnum e : values()) {
if (e.description.equals(desc)) {
return e;
}
}
return null;
}
}
// ❌ 不要这样做
public enum BadEnum {
// 不要使用无意义的名称
A, B, C,
// 不要暴露可变字段
value; // 应该是private final
// 不要在枚举中维护可变状态
private static int counter; // 危险!
// 不要在构造器中做复杂操作
BadEnum() {
// 可能抛出异常
}
}
3. 枚举性能考虑
public class EnumPerformance {
// 枚举比较的性能
enum Size { SMALL, MEDIUM, LARGE }
public static void main(String[] args) {
Size size = Size.MEDIUM;
// ✅ 推荐:使用 == 比较(性能好)
if (size == Size.MEDIUM) {
// 使用 == 比较枚举常量
}
// ✅ 推荐:使用 switch(性能好)
switch (size) {
case SMALL: break;
case MEDIUM: break;
case LARGE: break;
}
// ❌ 不推荐:使用 equals(性能稍差)
if (size.equals(Size.MEDIUM)) {
// 也可以,但性能不如 ==
}
System.out.println("枚举性能提示:");
System.out.println("1. 使用 == 比较枚举,不要用 equals()");
System.out.println("2. 枚举的 switch 语句非常高效");
System.out.println("3. 枚举的 values() 方法每次返回新数组,需要时缓存");
System.out.println("4. 大量使用枚举不会影响性能");
}
}
🎓 总结要点
- 枚举本质是类:继承自
java.lang.Enum - 构造器私有:不能手动创建枚举实例
- 线程安全:枚举实例在类加载时创建
- 支持接口:枚举可以实现接口
- 支持方法:可以有属性、方法、构造器
- 适合单例:枚举是实现单例的最佳方式
- 类型安全:编译时检查,避免错误值
记住:当需要表示一组固定的常量时,优先考虑使用枚举!