代码之家  ›  专栏  ›  技术社区  ›  chaotic3quilibrium

如何在编译时验证Java枚举符合两种不同类型

  •  -1
  • chaotic3quilibrium  · 技术社区  · 2 年前

    考虑到下面列出的五个类的代码,我有两个关于在编译时解决类型的问题(明确地说不是在运行时)。

    1. 在里面 DbIdEnumOps 在私有构造函数中,我如何验证 enumClassE 通过也符合 Class<EE> ?
    2. 在里面 Main main 方法,如何获得 a 推断它显示为 Integer 而不是 Object ,而不诉诸 b 我在哪里提供显式类型提示?

    我花了几个小时尝试不同的切线来实现这一点。下面的代码是我所能得到的最接近的代码。


    类:EnumOps.java

    public final class EnumOps<E extends Enum<E>> {
      private final Class<E> enumClass;
      private final List<E> enumsValues;
    
      private EnumOps(Class<E> enumClass) {
        this.enumClass = enumClass;
        this.enumsValues = Collections.unmodifiableList(Arrays.asList(enumClass.getEnumConstants()));
      }
    
      public static <E extends Enum<E>> EnumOps<E> from(Class<E> enumClass) {
        return new EnumOps<>(enumClass);
      }
    
      public Class<E> getEnumClass() {
        return this.enumClass;
      }
    
      public List<E> toList() {
        return this.enumsValues;
      }
    
      //omitted lots of other helpful enum utilities that ought to have been provided by the Java compiler by default
    }
    

    接口:DbIdEnum.java

    public interface DbIdEnum<T> {
      T getDbId();
    }
    

    类:DbIdEnumOps.java

    public final class DbIdEnumOps<E extends Enum<E>, EE extends DbIdEnum<T>, T> {
      private final Map<T, E> enumValueByDbId;
      private final Map<String, E> enumValueByNameLowerCaseOrDbId;
    
      private DbIdEnum<T> toDbIdEnum(E e) {
        return ((DbIdEnum<T>) e);
      }
    
      public static <E extends Enum<E>, EE extends DbIdEnum<T>, T> DbIdEnumOps<E, EE, T> from(Class<E> enumClassE) {
        return new DbIdEnumOps<>(enumClassE);
      }
    
      private DbIdEnumOps(Class<E> enumClassE) {
        //how to validate AT COMPILE TIME `enumClassE` also conforms to `Class<EE>`?
        var enumOpsList =
            EnumOps.from(Objects.requireNonNull(enumClassE))
                .toList();
        this.enumValueByDbId =
            Collections.unmodifiableMap(
                enumOpsList
                    .stream()
                    .map(e -> Map.entry(toDbIdEnum(e).getDbId(), e))
                    .collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
        this.enumValueByNameLowerCaseOrDbId =
            Collections.unmodifiableMap(
                enumOpsList
                    .stream()
                    .flatMap(e -> Stream.of(
                        Map.entry(e.name().toLowerCase(), e),
                        Map.entry(toDbIdEnum(e).getDbId().toString(), e)))
                    .collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
      }
    
      public Map<T, E> getEnumValueByDbId() {
        return this.enumValueByDbId;
      }
    
      public Map<String, E> getEnumValueByNameLowerCaseOrDbId() {
        return this.enumValueByNameLowerCaseOrDbId;
      }
    }
    

    枚举:TrafficLight.java

    public enum TrafficLight implements DbIdEnum<Integer> {
      GREEN(1),
      YELLOW(2),
      RED(3);
    
      private final Integer dbId;
    
      TrafficLight(Integer dbId) {
        this.dbId = dbId;
      }
    
      private static final EnumOps<TrafficLight> enumOps =
          EnumOps.from(TrafficLight.class);
      private static final DbIdEnumOps<TrafficLight, DbIdEnum<Integer>, Integer> dbIdEnumOpsA =
          DbIdEnumOps.from(TrafficLight.class);
    
      public static EnumOps<TrafficLight> ops() {
        return enumOps;
      }
    
      public static DbIdEnumOps<TrafficLight, DbIdEnum<Integer>, Integer> dbIdOps() {
        return dbIdEnumOpsA;
      }
    
      public Integer getDbId() {
        return this.dbId;
      }
    }
    

    类别:Main.java

    public class Main {
    
      public static void main(String[] args) {
        //a is inferred as: DbIdEnumOps<TrafficLight, DbIdEnum<Object>, Object>
        var a = DbIdEnumOps.from(TrafficLight.class);
        var b = DbIdEnumOps.<TrafficLight, DbIdEnum<Integer>, Integer>from(TrafficLight.class);
        DbIdEnumOps<TrafficLight, DbIdEnum<Integer>, Integer> c = DbIdEnumOps.from(TrafficLight.class);
        System.out.println("use me to set a breakpoint to examine the contents of the maps");
    
        //How do I get `a` to infer where it shows as `Integer`, instead of `Object`, without resorting to `b` where I provide an explicit type hint?
      }
    }
    

    对于1,我取的一条切线涉及到 enum 上课两次 from 方法,其中方法的签名如下所示:

      public static <E extends Enum<E>, EE extends DbIdEnum<T>, T> DbIdEnumOps<E, EE, T> from(Class<EE> enumClassEe, Class<E> enumClassE) {
        return new DbIdEnumOps<>(enumClassEe, enumClassE);
      }
    
      private DbIdEnumOps(Class<EE> enumClassEe, Class<E> enumClassE) {...
    

    虽然我做到了这一点,但仅仅为了满足这两种类型,传入两次枚举看起来并不正确。

    我认为有一些神秘或不寻常的类型规范路径仍然在我的理解范围之外。如有任何指导,我们将不胜感激。


    背景: 对于那些对上下文更好奇的人,我正试图删除(不要重复自己) 枚举 s在多个代码库中使用。许多枚举(但不是全部)用于数据库列。因此 EnumOps DbIdEnumOps

    1 回复  |  直到 2 年前
        1
  •  2
  •   Turing85 Oleksandr Pyrohov    2 年前

    我们可以通过 & .如果我们想要一些 E 成为 Enum<E> 并实现一些 interface Foo ,我们可以写 E extends Enum<E> & Foo 当然 Foo 也可以有一个泛型参数,我们可以将其绑定(例如。 E extends Enum<E> & Foo<Integer> )或者引入一个附加的通用参数( I, E extends Enum<E> & Foo<I> )。综合起来,我们得到:

    class Ideone {
      public static void main(String[] args) {
        boo(Bar.bar1);
    //    foo(new Baz()); // should not work - does not work
    //    foo(Bang.bang1); // should not work - does not work
      }
    
      public static <T extends Enum<T> & Foo<Integer>> void boo(T t) {
        System.out.println(t.ordinal());
        System.out.println(t.foo());
      }
    }
    
    interface Foo<T> {
      T foo();
    }
    
    enum Bar implements Foo<Integer> {
      bar1(1);
    
      final int foo;
    
      Bar(int foo) {
        this.foo = foo;
      }
    
      @Override
      public Integer foo() {
        return foo;
      }
    }
    
    class Baz implements Foo<Integer> {
      @Override
      public Integer foo() {
        return 42;
      }
    }
    
    enum Bang {
      bang1
    }
    

    Ideone.com demo