如何为EnumSet编写通用JAXB适配器实现序列化与反序列化?
JAXB序列化/反序列化EnumSet的解决方案
你遇到的核心问题是Java泛型的类型擦除——运行时EnumSetAdapter<E>中的泛型参数E会被擦除为Enum,JAXB实例化适配器时无法获取字段对应的具体Enum类型,导致unmarshal阶段无法调用Enum.valueOf。原思路的方向没错,但通用泛型适配器无法直接在JAXB中生效,因为缺少类型信息传递的途径。
方案一:带Enum类参数的适配器+具体子类
先修改适配器,增加构造方法接收Enum的Class对象,解决类型信息缺失的问题:
import java.util.EnumSet; import java.util.Arrays; import java.util.stream.Collectors; import javax.xml.bind.annotation.adapters.XmlAdapter; public class EnumSetAdapter<E extends Enum<E>> extends XmlAdapter<String, EnumSet<E>> { private static final String DELIMITER = ","; private final Class<E> enumClass; // 传入具体Enum的Class对象 public EnumSetAdapter(Class<E> enumClass) { this.enumClass = enumClass; } @Override public EnumSet<E> unmarshal(String v) throws Exception { if (v == null || v.isBlank()) { return EnumSet.noneOf(enumClass); } return Arrays.stream(v.split(DELIMITER)) .map(String::trim) .map(name -> Enum.valueOf(enumClass, name)) .collect(Collectors.toCollection(() -> EnumSet.noneOf(enumClass))); } @Override public String marshal(EnumSet<E> v) throws Exception { if (v == null || v.isEmpty()) { return null; } return v.stream() .map(Enum::name) .collect(Collectors.joining(DELIMITER)); } }
由于JAXB要求适配器必须有无参构造,因此需要为每个具体Enum编写对应的适配器子类:
// 针对MyEnumType的专属适配器 public class MyEnumTypeSetAdapter extends EnumSetAdapter<MyEnumType> { public MyEnumTypeSetAdapter() { // 传入具体Enum的Class super(MyEnumType.class); } }
最后在字段上使用子类适配器:
@XmlJavaTypeAdapter(MyEnumTypeSetAdapter.class) private final EnumSet<MyEnumType> status;
方案二:抽象适配器(更简洁的写法)
定义一个抽象适配器,让具体子类实现获取EnumClass的方法,代码结构更清晰:
import java.util.EnumSet; import java.util.Arrays; import java.util.stream.Collectors; import javax.xml.bind.annotation.adapters.XmlAdapter; public abstract class AbstractEnumSetAdapter<E extends Enum<E>> extends XmlAdapter<String, EnumSet<E>> { private static final String DELIMITER = ","; // 由子类实现,返回具体Enum的Class protected abstract Class<E> getEnumClass(); @Override public EnumSet<E> unmarshal(String v) throws Exception { if (v == null || v.isBlank()) { return EnumSet.noneOf(getEnumClass()); } return Arrays.stream(v.split(DELIMITER)) .map(String::trim) .map(name -> Enum.valueOf(getEnumClass(), name)) .collect(Collectors.toCollection(() -> EnumSet.noneOf(getEnumClass()))); } @Override public String marshal(EnumSet<E> v) throws Exception { if (v == null || v.isEmpty()) { return null; } return v.stream().map(Enum::name).collect(Collectors.joining(DELIMITER)); } }
具体Enum的适配器只需实现抽象方法:
public class MyEnumTypeSetAdapter extends AbstractEnumSetAdapter<MyEnumType> { @Override protected Class<MyEnumType> getEnumClass() { return MyEnumType.class; } }
用法和方案一一致,在字段上标注@XmlJavaTypeAdapter(MyEnumTypeSetAdapter.class)即可。
为什么通用泛型适配器不可行?
JAXB在处理@XmlJavaTypeAdapter(EnumSetAdapter.class)时,会通过无参构造实例化适配器,但由于泛型类型擦除,运行时适配器无法知道当前处理的是哪个Enum类型——E的具体类型信息在编译后就丢失了,因此无法动态获取Enum的Class对象来完成反序列化。
内容的提问来源于stack exchange,提问作者tomorrow
相关产品推荐
相关产品推荐

