You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在编译时验证Java枚举符合双类型并优化类型推断

Java编译时类型解析问题

核心问题

  1. 如何在DbIdEnumOps的私有构造方法中编译时验证传入的enumClassE同时属于Class<EE>类型?
  2. 如何让Main类中变量a自动推断为DbIdEnumOps<TrafficLight, DbIdEnum<Integer>, Integer>,而非默认的DbIdEnumOps<TrafficLight, DbIdEnum<Object>, Object>,且不需要像变量b那样显式指定泛型参数?

现有代码

Class: EnumOps.java

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

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;
  }

  // 省略其他枚举工具方法
}

Interface: DbIdEnum.java

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

Class: DbIdEnumOps.java

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.AbstractMap.SimpleEntry;

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) {
    // 如何编译时验证 enumClassE 同时符合 Class<EE> 类型?
    var enumOpsList =
        EnumOps.from(Objects.requireNonNull(enumClassE))
            .toList();
    this.enumValueByDbId =
        Collections.unmodifiableMap(
            enumOpsList
                .stream()
                .map(e -> new SimpleEntry<>(toDbIdEnum(e).getDbId(), e))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
    this.enumValueByNameLowerCaseOrDbId =
        Collections.unmodifiableMap(
            enumOpsList
                .stream()
                .flatMap(e -> Stream.of(
                    new SimpleEntry<>(e.name().toLowerCase(), e),
                    new SimpleEntry<>(toDbIdEnum(e).getDbId().toString(), e)))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
  }

  public Map<T, E> getEnumValueByDbId() {
    return this.enumValueByDbId;
  }

  public Map<String, E> getEnumValueByNameLowerCaseOrDbId() {
    return this.enumValueByNameLowerCaseOrDbId;
  }
}

Enum: 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;
  }
}

Class: Main.java

public class Main {

  public static void main(String[] args) {
    // a 被推断为 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");

    // 如何让 a 自动推断出 Integer 类型,无需显式类型提示?
  }
}

已尝试的方案(问题1)

曾尝试修改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) {
  // ... 构造逻辑
}

解决方案

问题1:编译时验证enumClassE属于Class<EE>

当前泛型参数EE是多余的,且无法和E建立编译时约束。可以通过调整泛型声明,让E同时继承Enum<E>和DbIdEnum<T>,这样就能直接在编译时确保传入的枚举类实现了DbIdEnum<T>,无需额外的EE参数:

修改DbIdEnumOps的泛型声明和构造逻辑:

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.AbstractMap.SimpleEntry;

public final class DbIdEnumOps<E extends Enum<E> & DbIdEnum<T>, T> {
  private final Map<T, E> enumValueByDbId;
  private final Map<String, E> enumValueByNameLowerCaseOrDbId;

  public static <E extends Enum<E> & DbIdEnum<T>, T> DbIdEnumOps<E, T> from(Class<E> enumClass) {
    return new DbIdEnumOps<>(enumClass);
  }

  private DbIdEnumOps(Class<E> enumClass) {
    // 编译时已确保 enumClass 是实现了 DbIdEnum<T> 的枚举类,无需强制转换
    var enumOpsList = EnumOps.from(Objects.requireNonNull(enumClass)).toList();
    this.enumValueByDbId = Collections.unmodifiableMap(
        enumOpsList.stream()
            .map(e -> new SimpleEntry<>(e.getDbId(), e))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
    this.enumValueByNameLowerCaseOrDbId = Collections.unmodifiableMap(
        enumOpsList.stream()
            .flatMap(e -> Stream.of(
                new SimpleEntry<>(e.name().toLowerCase(), e),
                new SimpleEntry<>(e.getDbId().toString(), e)))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
  }

  public Map<T, E> getEnumValueByDbId() {
    return this.enumValueByDbId;
  }

  public Map<String, E> getEnumValueByNameLowerCaseOrDbId() {
    return this.enumValueByNameLowerCaseOrDbId;
  }
}

修改后效果:

  • 泛型参数E被约束为同时是枚举类且实现DbIdEnum<T>,编译时如果传入的类不满足这个条件会直接报错
  • 移除了多余的EE参数,代码更简洁,且无需强制转换E为DbIdEnum<T>

问题2:让变量a自动推断正确类型

通过上述修改DbIdEnumOps的泛型声明后,类型推断会自动获取T的实际类型(比如TrafficLight的Integer),此时Main类中的变量a会被正确推断为DbIdEnumOps<TrafficLight, Integer>,无需显式指定泛型参数:

修改后的Main类:

public class Main {
  public static void main(String[] args) {
    // 现在 a 会被自动推断为 DbIdEnumOps<TrafficLight, Integer>
    var a = DbIdEnumOps.from(TrafficLight.class);
    System.out.println("use me to set a breakpoint to examine the contents of the maps");
  }
}

同时,TrafficLight类中的静态变量声明也可以简化:

private static final DbIdEnumOps<TrafficLight, Integer> dbIdEnumOpsA =
    DbIdEnumOps.from(TrafficLight.class);

public static DbIdEnumOps<TrafficLight, Integer> dbIdOps() {
  return dbIdEnumOpsA;
}

说明

这种修改既满足了DRY原则,又清晰分离了EnumOps(通用枚举工具)和DbIdEnumOps(数据库ID关联枚举工具)的职责,同时解决了两个编译时类型问题。

内容的提问来源于stack exchange,提问作者chaotic3quilibrium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:08:09