如何在编译时验证Java枚举符合双类型并优化类型推断
Java编译时类型解析问题
核心问题
- 如何在
DbIdEnumOps的私有构造方法中编译时验证传入的enumClassE同时属于Class<EE>类型? - 如何让
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
相关产品推荐
相关产品推荐

