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

问题描述 投票:0回答:1

鉴于下面列出的跨五个类的代码,我有两个关于在编译时解析类型的问题(明确不是在运行时)。

  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) {...

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

我认为有一些神秘或不寻常的类型规范路径仍然超出了我的理解。任何有关这方面的指导将不胜感激。


背景:对于那些对上下文更好奇的人,我正在尝试干燥(不要重复自己)一堆在多个代码库中使用的

enum
。许多(但不是全部)枚举用于数据库列。因此,
EnumOps
DbIdEnumOps
之间的关注点明确(且必需)分离。

java generics enums interface
1个回答
0
投票

我们可以通过

&
限制泛型类型以符合多种实现。如果我们希望某个
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
演示

© www.soinside.com 2019 - 2024. All rights reserved.