1. 概述

枚举(enum)是Java语言中强大且广泛使用的特性。在某些场景下,我们需要将一种枚举类型转换为另一种。这种需求可能出现在集成不同库或框架、使用不同平台的微服务,或处理难以更新的遗留代码时。

本文将探讨在Java中映射或转换枚举的不同技术,包括内置机制和外部库的辅助方案。

2. 定义模型

在处理枚举转换时,主要会遇到两种情况:

  • 无关枚举:值集完全不同
  • 同值枚举:值相同但属于不同类(Java视角)

为演示技术,我们定义两个数据模型。同值枚举模型

public enum OrderStatus {
    PENDING, APPROVED, PACKED, DELIVERED;
}
public enum CmsOrderStatus {
    PENDING, APPROVED, PACKED, DELIVERED;
}

不同值枚举模型

public enum UserStatus {
    PENDING, ACTIVE, BLOCKED, INACTIVATED_BY_SYSTEM, DELETED;
}
public enum ExternalUserStatus {
    ACTIVE, INACTIVE
}

3. 使用Java核心

大多数枚举转换可通过Java核心能力实现,无需外部库。

3.1 使用Switch语句

最直接的方式是使用switch机制。通过为每个枚举常量创建条件,确定对应的转换值。注意语法随Java版本演变

  • ✅ **Java 14+**(正式版)或 **Java 12+**(预览版)支持switch表达式和多值case:

    public ExternalUserStatus toExternalUserStatusViaSwitchStatement() {
      return switch (this) {
          case PENDING, BLOCKED, INACTIVATED_BY_SYSTEM, DELETED -> ExternalUserStatus.INACTIVE;
          case ACTIVE -> ExternalUserStatus.ACTIVE;
      };
    }
    
  • 传统Java版本需用普通switch:

    public ExternalUserStatus toExternalUserStatusViaRegularSwitch() {
      switch (this) {
      case PENDING:
      case BLOCKED:
      case INACTIVATED_BY_SYSTEM:
      case DELETED:
          return ExternalUserStatus.INACTIVE;
      case ACTIVE:
          return ExternalUserStatus.ACTIVE;
      }
      return null;
    }
    

测试示例:

@Test
void whenUsingSwitchStatement_thenEnumConverted() {
    UserStatus userStatusDeleted = UserStatus.DELETED;
    UserStatus userStatusPending = UserStatus.PENDING;
    UserStatus userStatusActive = UserStatus.ACTIVE;

    assertEquals(ExternalUserStatus.INACTIVE, userStatusDeleted.toExternalUserStatusViaSwitchStatement());
    assertEquals(ExternalUserStatus.INACTIVE, userStatusPending.toExternalUserStatusViaSwitchStatement());
    assertEquals(ExternalUserStatus.ACTIVE, userStatusActive.toExternalUserStatusViaSwitchStatement());
}

@Test
void whenUsingSwitch_thenEnumConverted() {
    UserStatus userStatusDeleted = UserStatus.DELETED;
    UserStatus userStatusPending = UserStatus.PENDING;
    UserStatus userStatusActive = UserStatus.ACTIVE;

    assertEquals(ExternalUserStatus.INACTIVE, userStatusDeleted.toExternalUserStatusViaRegularSwitch());
    assertEquals(ExternalUserStatus.INACTIVE, userStatusPending.toExternalUserStatusViaRegularSwitch());
    assertEquals(ExternalUserStatus.ACTIVE, userStatusActive.toExternalUserStatusViaRegularSwitch());
}

💡 转换逻辑不一定需放在枚举类内,但封装逻辑更优。

3.2 使用成员变量

通过在枚举中定义字段实现转换。为UserStatus添加externalUserStatus字段,在常量声明中指定映射值:

public enum UserStatusWithFieldVariable {
    PENDING(ExternalUserStatus.INACTIVE),
    ACTIVE(ExternalUserStatus.ACTIVE),
    BLOCKED(ExternalUserStatus.INACTIVE),
    INACTIVATED_BY_SYSTEM(ExternalUserStatus.INACTIVE),
    DELETED(ExternalUserStatus.INACTIVE);

    private final ExternalUserStatus externalUserStatus;

    UserStatusWithFieldVariable(ExternalUserStatus externalUserStatus) {
        this.externalUserStatus = externalUserStatus;
    }
}

转换方法:

public ExternalUserStatus toExternalUserStatus() {
    return externalUserStatus;
}

3.3 使用EnumMap

当无法修改源码或需将转换逻辑外置时,EnumMap是理想选择。专为枚举键优化,避免使用其他Map类型:

public class UserStatusMapper {
    public static EnumMap<UserStatus, ExternalUserStatus> statusesMap;
    static {
        statusesMap = new EnumMap<>(UserStatus.class);
        statusesMap.put(UserStatus.PENDING, ExternalUserStatus.INACTIVE);
        statusesMap.put(UserStatus.BLOCKED, ExternalUserStatus.INACTIVE);
        statusesMap.put(UserStatus.DELETED, ExternalUserStatus.INACTIVE);
        statusesMap.put(UserStatus.INACTIVATED_BY_SYSTEM, ExternalUserStatus.INACTIVE);
        statusesMap.put(UserStatus.ACTIVE, ExternalUserStatus.ACTIVE);
    }
}

3.4 使用枚举名称

当枚举值完全相同时,可利用Java的valueOf()方法。注意名称必须严格匹配,否则抛出IllegalArgumentException

public CmsOrderStatus toCmsOrderStatus() {
    return CmsOrderStatus.valueOf(this.name());
}

⚠️ 谨慎使用此方案,尤其处理外部枚举时,名称不匹配会导致运行时错误。

3.5 使用序数方法

序数方法利用枚举内部数组实现索引访问,但存在风险

public CmsOrderStatus toCmsOrderStatusOrdinal() {
    return CmsOrderStatus.values()[this.ordinal()];
}

测试示例:

@Test
void whenUsingOrdinalApproach_thenEnumConverted() {
    OrderStatus orderStatusApproved = OrderStatus.APPROVED;
    OrderStatus orderStatusDelivered = OrderStatus.DELIVERED;
    OrderStatus orderStatusPending = OrderStatus.PENDING;

    assertEquals(CmsOrderStatus.APPROVED, orderStatusApproved.toCmsOrderStatusOrdinal());
    assertEquals(CmsOrderStatus.DELIVERED, orderStatusDelivered.toCmsOrderStatusOrdinal());
    assertEquals(CmsOrderStatus.PENDING, orderStatusPending.toCmsOrderStatusOrdinal());
}

不推荐使用:当枚举大小变化时会导致运行时失败。Java文档明确指出:

序数方法主要用于EnumSetEnumMap等复杂数据结构。

4. 使用MapStruct

MapStruct是流行的实体映射库,也适用于枚举转换。

4.1 Maven依赖

添加MapStruct依赖和注解处理器:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <source>17</source>
        <target>17</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.5.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

4.2 使用方式

MapStruct基于注解生成实现。自动处理同名枚举,需显式配置不同名映射:

@Mapper
public interface EnumMapper {

    CmsOrderStatus map(OrderStatus orderStatus);

    @ValueMapping(source = "PENDING", target = "INACTIVE")
    @ValueMapping(source = "BLOCKED", target = "INACTIVE")
    @ValueMapping(source = "INACTIVATED_BY_SYSTEM", target = "INACTIVE")
    @ValueMapping(source = "DELETED", target = "INACTIVE")
    ExternalUserStatus map(UserStatus userStatus);
}

💡 UserStatus.ACTIVEExternalUserStatus.ACTIVE会自动映射。

使用ANY_REMAINING设置默认值:

@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "INACTIVE")
ExternalUserStatus mapDefault(UserStatus userStatus);

5. 最佳实践和使用场景

选择转换策略需考虑项目需求:

  • 枚举常量相似度
  • 源码访问权限
  • 枚举变更频率

推荐方案对比

场景 推荐方案 优点 缺点
✅ 可修改源码 Switch/成员变量 逻辑封装,代码组织好 复杂条件时代码冗长
✅ 不可修改源码 EnumMap 映射清晰,无需switch 需维护映射关系
✅ 枚举名称完全相同 枚举名称方法 简单直接 名称不匹配时崩溃
❌ 避免使用 序数方法 特定场景高效 枚举变更时风险高
✅ 自动化需求 MapStruct 支持同名/异名枚举,可配置默认值 引入额外依赖

关键原则

  1. 枚举变更时立即更新映射
  2. 无法更新时
    • 设置默认映射值
    • 优雅处理新枚举值
  3. 优先选择稳定方案:避免序数方法,谨慎使用名称匹配

6. 结论

本文探讨了Java中枚举映射的多种技术,包括内置机制和MapStruct库。没有万能方案,需根据具体场景选择:

  • 同名枚举 → 枚举名称方法
  • 可修改源码 → Switch/成员变量
  • 不可修改源码 → EnumMap
  • 复杂映射需求 → MapStruct

完整示例代码见GitHub仓库


原始标题:Convert One Enum to Another Enum in Java