如何让MapStruct自动映射枚举值至伪枚举类常量?
如何用MapStruct自动实现枚举与伪枚举类常量的双向映射
问题背景
代码库中存在大量如下结构的伪枚举类:
public final class Target extends PseudoEnum { public static final Target FOO = new Target(1); public static final Target BAR = new Target(2); private Target(int value) { super(value); } }
需要与标准枚举Source做双向映射:
public enum Source { FOO, BAR }
直接使用MapStruct的@ValueMapping会失败(因为Target不是标准枚举),手动维护映射Map又过于繁琐,希望能实现自动映射。
可行解决方案
方案一:通用反射转换器(推荐)
利用反射根据常量名称匹配的约定,编写通用转换器,一次性解决所有同结构伪枚举的映射问题,无需为每个伪枚举编写重复代码。
1. 编写通用转换器类
import java.lang.reflect.Field; import java.util.Arrays; public class PseudoEnumConverter { // 标准枚举转伪枚举 @SuppressWarnings("unchecked") public static <T extends PseudoEnum> T enumToPseudoEnum(Enum<?> source, Class<T> targetClass) { if (source == null) { return null; } try { // 通过枚举名称匹配伪枚举的静态常量 Field field = targetClass.getField(source.name()); return (T) field.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { throw new IllegalArgumentException("无法匹配对应伪枚举常量: " + source.name(), e); } } // 伪枚举转标准枚举 public static <E extends Enum<E>> E pseudoEnumToEnum(PseudoEnum source, Class<E> enumClass) { if (source == null) { return null; } try { // 遍历伪枚举的静态常量,找到匹配实例后返回对应枚举 Field matchingField = Arrays.stream(source.getClass().getFields()) .filter(field -> { try { return field.get(null).equals(source); } catch (IllegalAccessException e) { return false; } }) .findFirst() .orElseThrow(() -> new IllegalArgumentException("无法找到对应枚举值: " + source)); return Enum.valueOf(enumClass, matchingField.getName()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("无法匹配对应枚举值", e); } } }
2. 在MapStruct Mapper中使用转换器
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper public interface SourceMapper { SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class); default Target map(Source source) { return PseudoEnumConverter.enumToPseudoEnum(source, Target.class); } default Source map(Target target) { return PseudoEnumConverter.pseudoEnumToEnum(target, Source.class); } }
这种方式的优势是:只要伪枚举的静态常量名与枚举名完全一致,新增常量时无需修改映射代码,完全遵循约定即可自动映射,适合大量伪枚举的场景。
方案二:半自动映射(无反射)
如果不希望使用反射,可以结合MapStruct的@ValueMapping注解和自定义方法,让映射代码更符合MapStruct的风格,缺点是新增常量时需要手动更新映射关系:
import org.mapstruct.Mapper; import org.mapstruct.ValueMapping; import org.mapstruct.factory.Mappers; @Mapper public interface SourceMapper { SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class); @ValueMapping(source = "FOO", target = "FOO") @ValueMapping(source = "BAR", target = "BAR") default Target map(Source source) { return switch(source) { case FOO -> Target.FOO; case BAR -> Target.BAR; }; } @ValueMapping(source = "FOO", target = "FOO") @ValueMapping(source = "BAR", target = "BAR") default Source map(Target target) { if (target == Target.FOO) return Source.FOO; else if (target == Target.BAR) return Source.BAR; throw new IllegalArgumentException("未知伪枚举值: " + target); } }
关于MapStruct官方支持
MapStruct目前没有原生支持类静态常量与枚举的自动映射,因为伪枚举本质是普通类的静态实例,而非JDK标准枚举类型,因此需要通过自定义逻辑来实现自动化映射。
内容的提问来源于stack exchange,提问作者Stov
相关产品推荐
相关产品推荐

