【问题标题】:Java - Generic abstract class for Enum ConverterJava - 枚举转换器的通用抽象类
【发布时间】:2020-05-24 06:45:18
【问题描述】:

我有一些枚举是 name("String") 和 value("Integer") 对,当前在每个枚举中我必须为 Json Deserialize 实现一个 @JsonCreator 方法,输入可以是名称或值

//User Enum
public enum UserType {
   StakeHolder(1),...

   private static final Map<String, UserType> helper = ImmutableMap.<String, UserType>builder()
            .put(String.valueOf(StakeHolder.getValue()), StakeHolder)
            .put(StakeHolder.name(), StakeHolder)
            .build();

   @JsonCreator
   public static UserType jsonDeserialize(String json) {
        return helper.get(json);
   }
}

//Role Enum
public enum RoleType {
   Admin(1),...

   private static final Map<String, RoleType> helper = ImmutableMap.<String, RoleType>builder()
            .put(String.valueOf(Admin.getValue()), Admin)
            .put(Admin.name(), Admin)
            .build();

   @JsonCreator
   public static RoleType jsonDeserialize(String json) {
        return helper.get(json);
   }
}

但我想知道是否有任何方法可以创建一个可以实现整体枚举的抽象类,这是为了避免在我的每个枚举中始终实现类似的“重复”代码。当前的代码就像

public abstract class JsonDeserializeEnum<E extends Enum<E>> {
  // protected Enum<E> myEnums;
  // private static final Map<String, E> myEnumHelper; ???

  public JsonDeserializeEnum() {
    super();
    // how to loop through myEnums to do something like
    // for(E iterator : myEnums.value()) not work...myEnums is not a collection of enums?
    // myEnumHelper.put(String.valueOf(iterator.getValue()), (E) iterator);
    // myEnumHelper.put(iterator.name, (E) iterator);
  } 

  @JsonCreator
   public static E jsonDeserialize(String json) {
        return myEnumHelper.get(json);
   }
}

//then
public enum UserType implements JsonDeserializeEnum<UserType>{
    StakeHolder(1),...
}
public enum RoleType implements JsonDeserializeEnum<RoleType>{
    Admin(1),...
}

我在这里遇到了两个挑战,

1) 我想在我的抽象类中有一个初始化/构造函数,可以将所有可用的枚举加载到静态映射中(仅一次),例如,

调用UserType时,将所有用户类型枚举加载到静态映射中,然后从该映射中反序列化;调用RoleType时,将所有角色类型枚举加载到map中,然后从同一个map中反序列化

2)如果 No.1 是不可能的,那没关系,假设我是否会在我的抽象类中摆脱映射,但仍然使用我的每个枚举中定义的映射,我的抽象类是否知道并且可以使用我的枚举中定义的地图吗?

【问题讨论】:

  • 除了你提议的实现中的关键冲突之外,主要问题是jsonDeserialize() 必须static。这意味着您必须在每个枚举上声明它。您可以选择将查找逻辑外部化(例如通过将您的地图更改为在您的界面中使用枚举类型条目的地图地图),但这只会改善很少。
  • 明白了!假设我要摆脱抽象类中的映射,但仍然使用每个 Enum 中定义的映射,我的抽象类是否知道并且可以使用 Enum 中定义的映射?
  • 首先,你不能让你的枚举extend成为一个抽象类。你可以让它们实现一个接口。是的,你可以定义一个契约,让你的枚举实现一个返回本地地图的方法,但它必须是一个实例方法。当您回顾这样的实现时,您可能会发现您当前的版本比两个丑陋的设计更可取(我的观点)。

标签: java enums abstract-class generic-programming


【解决方案1】:

关键是使用JsonDeserializer。首先,您需要使用一个接口才能对所有枚举使用相同的代码:

public interface ValueEnum {
    String name();
    int value();
}

并在每个枚举中实现它

public enum EnumExample implements ValueEnum {

    Hello(1),
    World(2);

    public final int value;

    private EnumExample(int value) {
        this.value = value;
    }

    @Override
    public int value() {
        return value;
    }
}

name() 已经被 Enum 实现了)

然后你可以将你的地图创建移动到一个单独的类(我使用java HashMap,你可以使用任何你喜欢的)

public class ValueEnumDeserializer<T extends ValueEnum> extends JsonDeserializer<T> {

    private final HashMap<String, T> map;

    public ValueEnumDeserializer(Class<T> c) {
        map = new HashMap<>();
        for (final T i : c.getEnumConstants()) {
            map.put(i.name(), i);
            map.put(String.valueOf(i.value()), i);
        }
    }

    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return map.get(p.getText());
    }
}

然后,当您创建对象映射器时,您必须传递反序列化器

    final ObjectMapper mapper = new ObjectMapper();
    final SimpleModule module = new SimpleModule();
    module.addDeserializer(EnumExample1.class, new ValueEnumDeserializer<>(EnumExample1.class));
    module.addDeserializer(EnumExample2.class, new ValueEnumDeserializer<>(EnumExample2.class));
    module.addDeserializer(EnumExample3.class, new ValueEnumDeserializer<>(EnumExample3.class));
    mapper.registerModule(module);

【讨论】:

  • 哇,这太棒了!谢谢!几个我不太了解的问题 1)“ValueEnumDeserializer”中的“map”,每次反序列化 json 请求时是否都会初始化它,因为它在类的构造函数中但不是静态的? 2)在这里,您使用的是“JsonDeserializer”而不是“JsonCreator”,在“反序列化”方法中,“JsonParser”和“DeserializationContext”是否需要使用配置初始化某处(例如ignoreCamelCase或其他东西)?如果是这样,我应该在哪里初始化和注入它们? 3)是否可以将“模块”提取到单独的类中?
  • 1) 仅在每次创建新的 ObjectMapper 时,应该只有一次。 2) 配置由创建的 ObjectMapper 继承,但是忽略骆驼大小写和大多数其他是指属性名称(枚举值不是属性名称)。如果您想与一个枚举匹配多个字符串,您可以向反序列化器的映射添加更多值 3) 是的,只需创建扩展 SimpleModule 的 YourModule 并在构造函数中添加反序列化器
  • 无论如何我可以从我的枚举中重新使用ValueEnumDeserializer 中定义的映射或方法吗?例如,如果我需要一个转换器方法fromValue,它将接收一个整数值并将映射的枚举返回给我,例如 RoleType.fromValue(1) 将返回 Admin,我怎样才能更好地使用 ValueEnumDeserializer 中的通用概念?还是我必须在ValueEnum 接口中声明该方法?如果是这样,那么我将不得不通过在我的枚举中创建一个重复的映射来实现它,而在 ValueEnumDeserializer 中我已经有一个用于反序列化的映射
  • 你不能在接口中添加这样的方法,因为它是一个静态方法。在任何情况下,您都需要一个带有整数键而不是字符串的新映射。但是考虑到枚举成员的数量,您可以轻松地遍历所有成员并使用 values() 数组检查它们的值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多