Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
    • 异常处理
    • 基本类型与包装类
    • Object类与常用方法
    • 泛型
    • 枚举与注解
      • 1. 枚举的基本用法
      • 2. 枚举的本质
      • 3. 枚举字段和方法
      • 4. 枚举中的抽象方法
      • 5. 枚举常用方法
      • 6. switch 与枚举
      • 7. EnumSet 与 EnumMap
      • 8. 注解的基本概念
      • 9. 自定义注解
      • 10. 元注解
        • 10.1 Retention
        • 10.2 Target
        • 10.3 Documented 与 Inherited
      • 11. 运行时读取注解
      • 12. 编译期注解处理
      • 13. 枚举与注解的开发建议
        • 13.1 枚举建议
        • 13.2 注解建议
      • Tips 快问快答
    • 反射
    • 值传递与引用
    • Lambda表达式与函数式接口
    • Stream API
    • Optional与空值处理
    • 日期时间API
    • Java现代语法特性
  • Java集合

  • Java并发

  • Java IO

  • JVM

目录

枚举与注解

枚举和注解都是 Java 语言提供的“表达语义”的机制。

枚举用于表达一组有限、固定、类型安全的值;注解用于给代码附加元数据,让编译器、框架或运行时工具能够读取这些信息并做进一步处理。

在业务开发中,枚举和注解非常常见:

  • 订单状态、支付渠道、用户类型通常适合用枚举。
  • @Override、@Deprecated、@Transactional、@Controller、@Test 都是注解。
  • Spring、MyBatis、JUnit 等框架大量依赖注解和反射。

# 1. 枚举的基本用法

枚举使用 enum 定义。

public enum OrderStatus {
    CREATED,
    PAID,
    CANCELED,
    FINISHED
}

使用:

OrderStatus status = OrderStatus.CREATED;

if (status == OrderStatus.CREATED) {
    System.out.println("订单已创建");
}

枚举适合表达有限集合:

订单状态
CREATED -> PAID -> FINISHED
       └-> CANCELED

比起字符串常量,枚举有几个优势:

对比项 字符串常量 枚举
类型安全 弱,任意字符串都能传 强,只能传枚举值
编译期检查 较弱 强
可读性 一般 好
可承载行为 不方便 可以定义字段和方法
重构支持 弱 IDE 支持好

# 2. 枚举的本质

枚举本质上是继承 java.lang.Enum 的特殊类。每个枚举常量都是该枚举类的一个固定实例。

public enum PayType {
    WECHAT,
    ALIPAY
}

可以概念化理解为:

public final class PayType extends Enum<PayType> {
    public static final PayType WECHAT = new PayType("WECHAT", 0);
    public static final PayType ALIPAY = new PayType("ALIPAY", 1);

    private PayType(String name, int ordinal) {
        super(name, ordinal);
    }
}

结构:

PayType 类
├─ WECHAT  唯一实例
└─ ALIPAY  唯一实例

因此枚举构造器必须是私有的,不能在外部创建新枚举对象。

# 3. 枚举字段和方法

枚举可以有字段、构造器和方法。

public enum PayType {
    WECHAT("微信支付"),
    ALIPAY("支付宝支付");

    private final String description;

    PayType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}

使用:

System.out.println(PayType.WECHAT.getDescription());

业务枚举建议不要只写枚举名,可以加业务 code:

public enum OrderStatus {
    CREATED("CREATED", "已创建"),
    PAID("PAID", "已支付");

    private final String code;
    private final String text;

    OrderStatus(String code, String text) {
        this.code = code;
        this.text = text;
    }
}

这样便于和数据库、前端、外部接口交互。

# 4. 枚举中的抽象方法

枚举常量可以有自己的行为。

public enum DiscountType {
    NONE {
        BigDecimal calculate(BigDecimal price) {
            return price;
        }
    },
    HALF {
        BigDecimal calculate(BigDecimal price) {
            return price.multiply(new BigDecimal("0.5"));
        }
    };

    abstract BigDecimal calculate(BigDecimal price);
}

这种写法适合策略数量固定且逻辑较简单的场景。

结构:

DiscountType
├─ NONE  -> 原价
└─ HALF  -> 半价

如果策略复杂、依赖很多外部服务,更推荐使用策略类和依赖注入,而不是把复杂业务塞进枚举。

# 5. 枚举常用方法

方法 作用
name() 返回枚举常量名
ordinal() 返回声明顺序,从 0 开始
values() 返回所有枚举常量
valueOf(String) 按名称获取枚举
compareTo 按声明顺序比较

示例:

for (OrderStatus status : OrderStatus.values()) {
    System.out.println(status.name());
}

valueOf 找不到会抛异常:

OrderStatus.valueOf("UNKNOWN"); // IllegalArgumentException

开发中不要把 ordinal() 存入数据库。枚举顺序一旦调整,历史数据含义就会错乱。应存稳定的 code。

# 6. switch 与枚举

枚举适合配合 switch:

switch (status) {
    case CREATED -> System.out.println("待支付");
    case PAID -> System.out.println("已支付");
    case CANCELED -> System.out.println("已取消");
    case FINISHED -> System.out.println("已完成");
}

如果枚举新增值,编译器和 IDE 往往能提示 switch 未覆盖分支。

# 7. EnumSet 与 EnumMap

EnumSet 是专门为枚举优化的 Set。

EnumSet<OrderStatus> activeStatuses =
        EnumSet.of(OrderStatus.CREATED, OrderStatus.PAID);

EnumMap 是专门以枚举为 key 的 Map。

EnumMap<OrderStatus, String> textMap = new EnumMap<>(OrderStatus.class);
textMap.put(OrderStatus.CREATED, "待支付");

优势:

类型 优势
EnumSet 内部可用位向量表示,紧凑高效
EnumMap 以枚举 ordinal 定位,访问高效

当 key 或元素明确是枚举时,优先考虑 EnumSet 和 EnumMap。

# 8. 注解的基本概念

注解是附加在代码元素上的元数据。

@Override
public String toString() {
    return "User";
}

注解本身不直接改变代码逻辑。真正产生效果的是读取注解的工具:

源码上的注解
   │
   ├─ 编译器读取:检查语法、生成警告、生成代码
   ├─ 注解处理器读取:编译期生成文件
   └─ 框架运行时读取:反射扫描并执行逻辑

例如:

  • @Override 被编译器读取。
  • @Deprecated 被编译器和 IDE 读取。
  • @Transactional 被 Spring 运行时读取。
  • @Test 被 JUnit 读取。

# 9. 自定义注解

定义注解:

public @interface AuditLog {
    String value();

    boolean enabled() default true;
}

使用:

@AuditLog(value = "创建订单")
public void createOrder() {
}

注解元素规则:

  • 可以是基本类型、String、Class、枚举、注解或这些类型的数组。
  • 可以有默认值。
  • 不能有普通方法体。
  • 元素名为 value 且只设置它时,使用时可省略 value =。
@AuditLog("创建订单")
public void createOrder() {
}

# 10. 元注解

元注解是修饰注解的注解。

# 10.1 Retention

@Retention 定义注解保留到哪个阶段。

策略 保留阶段 典型场景
SOURCE 源码 编译检查,如 @Override
CLASS 字节码 编译期工具、字节码增强
RUNTIME 运行期 反射读取,如 Spring 注解

如果框架运行时需要读取注解,必须使用 RUNTIME。

@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
}

# 10.2 Target

@Target 定义注解可以放在哪里。

@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AuditLog {
}

常见目标:

目标 说明
TYPE 类、接口、枚举
METHOD 方法
FIELD 字段
PARAMETER 参数
CONSTRUCTOR 构造器
ANNOTATION_TYPE 注解类型

# 10.3 Documented 与 Inherited

@Documented 表示生成 Javadoc 时包含该注解。

@Inherited 表示类上的注解可以被子类继承。注意它只对类继承有效,对方法、字段无效。

# 11. 运行时读取注解

Method method = OrderService.class.getDeclaredMethod("createOrder");
AuditLog auditLog = method.getAnnotation(AuditLog.class);

if (auditLog != null && auditLog.enabled()) {
    System.out.println(auditLog.value());
}

读取流程:

Class / Method / Field
        │
        ▼
getAnnotation
        │
        ▼
获取注解代理对象
        │
        ▼
读取注解元素值

Spring AOP、接口文档工具、参数校验框架都大量使用这种模式。

# 12. 编译期注解处理

有些注解不是运行时读取,而是在编译期由注解处理器处理。

典型例子:

  • Lombok 根据注解生成 getter、setter、构造器等代码。
  • MapStruct 根据注解生成对象转换实现类。
  • 一些配置处理器生成元数据。

流程:

Java 源码
  │ javac
  ▼
注解处理器扫描注解
  │
  ├─ 生成新源码
  ├─ 生成配置文件
  └─ 输出编译警告或错误

编译期处理的好处是运行时开销低,但会增加编译链路复杂度。

# 13. 枚举与注解的开发建议

# 13.1 枚举建议

  • 业务枚举增加稳定 code,不依赖 ordinal()。
  • 对外接口传输 code,不直接暴露 Java 枚举名。
  • 枚举逻辑简单时可以封装方法,复杂时使用策略类。
  • 需要枚举集合时优先 EnumSet。
  • 需要枚举作为 key 时优先 EnumMap。

# 13.2 注解建议

  • 运行时读取的注解必须设置 @Retention(RUNTIME)。
  • 注解只表达元数据,复杂逻辑放到处理器、切面或框架组件中。
  • 自定义注解命名要表达明确语义。
  • 不要滥用注解隐藏业务流程,影响可读性。

# Tips 快问快答

Q:枚举可以被继承吗? A:不能。枚举本质上是特殊的 final 类,且已经继承 Enum。

Q:枚举可以实现接口吗? A:可以。枚举不能继承其他类,但可以实现接口。

Q:枚举构造器为什么不能 public? A:枚举常量必须固定,不能让外部随意创建实例。

Q:数据库里能存 ordinal() 吗? A:不推荐。枚举声明顺序变化会导致历史数据含义错乱。

Q:枚举比较用 == 还是 equals? A:通常用 ==,因为枚举常量是单例。

Q:注解本身会执行逻辑吗? A:不会。注解只是元数据,需要编译器、处理器或框架读取后才产生效果。

Q:为什么自定义注解运行时读不到? A:很可能没有设置 @Retention(RetentionPolicy.RUNTIME)。

Q:@Inherited 能让方法注解被子类继承吗? A:不能。它只对类级别注解继承有效。

Q:Lombok 注解是运行时生效吗? A:不是。Lombok 主要在编译期修改或生成代码。

Q:什么时候不要用注解? A:当注解让业务流程变得隐式、难搜索、难调试时,应考虑显式代码或配置。

上次更新: 2026/06/24, 16:28:28
泛型
反射

← 泛型 反射→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式