Skip to content

枚举和注解

Java 支持两种特殊用途的引用类型

  • 枚举类
  • 注解接口

1. 用 enum 代替 int 常量

Java 中的枚举类型 (enum) 比其他语言中的枚举类型强大得多,它是功能是分齐全的类,本质上是 int 值。

每当需要一组固定常量,并且在编译时就知道其成员的时候,就应该使用枚举

枚举的优势:

  • 可读性更好
  • 功能更强大
  • 更安全

2. 用实例域代替序数

许多枚举天生就与一个单独的 int 值相关联。所有的枚举都有一个 ordinal() 方法,它返回枚举常量的索引。

但永远不要根据枚举的这个索引值 (序数)导出与它关联的值,而是把它保存在实例域中

java
public enum Ensembel {
    SOLD,DUET,TRIO
    
    public int numberOfMusicians() {
        return ordinal() + 1;  ❌
    }
}

public enum Ensembel {
    SOLD(1),DUET(2),TRIO(3)  ✔
}

3. 用 EnumSet 代替位域

  • 传统位域(Bit Fields)的缺陷

位域通过二进制的位运算来表示多个状态标志,典型实现方式:

java
public class TextFormat {
    // 通过常量定义位标志
    public static final int BOLD = 1 << 0;     // 0001 (二进制)
    public static final int ITALIC = 1 << 1;   // 0010
    public static final int UNDERLINE = 1 << 2;// 0100

    // 组合使用位运算
    private int styles;

    public void applyStyles(int styles) {
        this.styles |= styles;
    }

    // 检测是否包含某样式
    public boolean isBold() {
        return (styles & BOLD) != 0;
    }
}
  • 使用EnumSet的优势

EnumSet是专门为枚举类型设计的高性能集合,底层通过位向量(bit vector)实现,兼具类型安全和位域的高效。

java
public enum TextStyle {
    BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}

public class TextFormatter {
    private EnumSet<TextStyle> styles = EnumSet.noneOf(TextStyle.class);

    // 应用样式(类型安全)
    public void applyStyles(Set<TextStyle> styles) {
        this.styles.addAll(styles);
    }

    // 检测是否包含某样式
    public boolean isBold() {
        return styles.contains(TextStyle.BOLD);
    }

    // 示例:动态组合样式
    public static void main(String[] args) {
        TextFormatter formatter = new TextFormatter();
        formatter.applyStyles(EnumSet.of(TextStyle.BOLD, TextStyle.ITALIC));
    }
}

4. 用 EnumMap 代替序数索引

java.util.EnumMap 是一种非常快速的专门用于枚举键的 Map 实现。

底层实现原理

  1. 键存储: 使用枚举类型的 ordinal() 值作为内部数组的索引
  2. 内存布局: 维护两个数组
    • Object[] vals:存储实际的值对象
    • Entry<?,?>[] table:支持快速查找的哈希表结构
  3. 时间复杂度O(1) 的查找性能 (比 HashMap 碰撞更少)

进阶应用场景

场景1:多级分类 (嵌套 EnumMap

java
enum Continent { ASIA, EUROPE, AFRICA }
enum Climate { TROPICAL, TEMPERATE, ARCTIC }

Map<Continent, Map<Climate, Set<Animal>>> animalDistribution = new EnumMap<>(Continent.class);

场景2:类型驱动的策略模式

java
interface Formatter { String format(Data data); }
enum DataType { CSV, JSON, XML }

Map<DataType, Formatter> formatters = new EnumMap<>(DataType.class);
formatters.put(DataType.JSON, new JsonFormatter());

5. 用接口模拟可扩展的枚举

虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型来对它进行模拟

java.nio.file.LinkOption 的枚举多接口实现

java
public enum LinkOption implements OpenOption, CopyOption {
    NOFOLLOW_LINKS;
}

在此案例中,LinkOption 同时实现以下核心接口:

  • OpenOption: 标识文件打开操作的配置选项
  • CopyOption: 标识文件复制操作的配置选项

这一设计的优点:

  • 复用性:单个枚举常量 NOFOLLOW_LINKS 可被传递给需要 OpenOptionCopyOption 参数的方法,如 Files.copy()Files.newInputStream()
  • 类型安全:编译器自动处理类型匹配,拒绝非法参数。
  • 语义明确:接口名称直接表明其适用场景,增强代码可读性。

6. 注解优先于命名模式

既然有了注解,就完全没有理由再使用命名模式了。

7. 坚持使用 Override 注解

覆盖的每个超类方法上都应该使用 @Override 注解

8. 用标记接口定义类型

标记接口(Marker Interface)是一个空接口,用于标识一个类型。