如何创建含带泛型参数Function字段的Java枚举并指定泛型
带泛型Function字段的Java枚举实现方案
需求场景
要在Java枚举中定义带泛型参数的Function字段,让每个枚举实例能处理对应类型的Foo<T>对象,最终返回String;要求扩展方便,避免冗余的运行时类型检查,且尽量不改用类实现。
期望代码示例
@Data @AllArgsConstructor public class Foo<T> { private T field; } @Getter @AllArgsConstructor public enum Bar { A((baz) -> { return SomeClass.someFunctionThatOnlyAcceptsStrings(baz.getField()); }), B((baz) -> { return SomeOtherClass.someFunctionThatOnlyAcceptsIntegers(baz.getField()); }); private Function<Foo<?>, String> func; } public class CoolGenerator { public static <T> String createCoolToken(T payload, Bar payloadType) { Foo<T> opts = new Foo<T>(payload); return payloadType.getFunc().apply(opts); } public static void main(String[] args) { String strToken = createCoolToken("someStr", Bar.A); String intToken = createCoolToken(13, Bar.B); System.out.println("strToken is " + strToken); System.out.println("intToken is " + intToken); } }
已尝试的三种方案及问题
方案1:带运行时类型检查(可运行但不优雅)
需要在lambda中手动判断类型,错误类型会返回null,易引发空指针:
@Data @AllArgsConstructor public class Foo<T> { private T field; } @Getter @AllArgsConstructor public enum Bar { A((baz) -> { if (baz.getField() instanceof String) { return SomeClass.someFunctionThatOnlyAcceptsStrings(baz.getField()); } return null; }), B((baz) -> { if (baz.getField() instanceof Integer) { return SomeOtherClass.someFunctionThatOnlyAcceptsIntegers(baz.getField()); } return null; }); private Function<Foo<?>, String> func; }
方案2:指定lambda参数具体泛型(编译报错)
报错信息:Lambda表达式的参数baz应为Foo<? extends Object>类型,原因是Function<Foo<String>, String>无法直接赋值给Function<Foo<?>, String>——Function的参数是逆变类型,Foo<?>是Foo<String>的父类型,编译器认为将窄类型的Function赋值给宽类型的Function会存在类型安全风险:
@Data @AllArgsConstructor public class Foo<T> { private T field; } @Getter @AllArgsConstructor public enum Bar { A((Foo<String> baz) -> { return SomeClass.someFunctionThatOnlyAcceptsStrings(baz.getField()); }), B((Foo<Integer> baz) -> { return SomeOtherClass.someFunctionThatOnlyAcceptsIntegers(baz.getField()); }); private Function<Foo<?>, String> func; }
方案3:重载构造函数(泛型擦除报错)
Java泛型擦除后,两个构造函数的签名会变为Bar(Function<Foo, String>),编译器判定为重复定义:
@Data @AllArgsConstructor public class Foo<T> { private T field; } @Getter public enum Bar { A((Foo<String> baz) -> { return SomeClass.someFunctionThatOnlyAcceptsStrings(baz.getField()); }), B((Foo<Integer> baz) -> { return SomeOtherClass.someFunctionThatOnlyAcceptsIntegers(baz.getField()); }); private Function<Foo<? extends Object>, String> func; private Bar(Function<Foo<String>, String> func) { this.func = func; } private Bar(Function<Foo<Integer>, String> func) { this.func = func; } }
正确实现方案
利用泛型捕获+类型标记的方式,既保证类型安全,又避免冗余检查,扩展方便:
1. Foo类保持不变
@Data @AllArgsConstructor public class Foo<T> { private T field; }
2. 优化后的Bar枚举
import java.util.function.Function; @Getter public enum Bar { // 新增类型只需添加枚举实例,指定对应Class和处理逻辑 A(String.class, foo -> SomeClass.someFunctionThatOnlyAcceptsStrings(foo.getField())), B(Integer.class, foo -> SomeOtherClass.someFunctionThatOnlyAcceptsIntegers(foo.getField())); // 存储当前枚举实例对应的处理类型 private final Class<?> type; // 统一的Function类型 private final Function<Foo<?>, String> func; // 泛型构造函数,捕获具体类型并做安全转换 private <T> Bar(Class<T> type, Function<Foo<T>, String> func) { this.type = type; // 转换是安全的:调用时会保证传入的Foo<T>与当前枚举实例的type匹配 this.func = (Foo<?> foo) -> func.apply((Foo<T>) foo); } // 安全的apply方法,提前校验类型匹配 public <T> String apply(Foo<T> foo) { if (!type.isInstance(foo.getField())) { throw new IllegalArgumentException("Payload type mismatch for enum: " + this.name()); } return func.apply(foo); } }
3. 调用类CoolGenerator
public class CoolGenerator { public static <T> String createCoolToken(T payload, Bar payloadType) { // 提前校验类型匹配,避免后续错误 if (!payloadType.getType().isInstance(payload)) { throw new IllegalArgumentException("Payload type does not match Bar enum type: " + payloadType.name()); } Foo<T> opts = new Foo<>(payload); return payloadType.apply(opts); } public static void main(String[] args) { String strToken = createCoolToken("someStr", Bar.A); String intToken = createCoolToken(13, Bar.B); // 以下代码会抛出异常,提前发现类型不匹配问题 // String wrongToken = createCoolToken(13, Bar.A); System.out.println("strToken is " + strToken); System.out.println("intToken is " + intToken); } }
方案优势
- 类型安全:调用层提前校验类型,构造函数的类型转换有安全保障,无需在lambda中做instanceof检查
- 扩展便捷:新增类型只需添加枚举实例,指定对应Class和处理逻辑即可
- 错误提前暴露:类型不匹配时直接抛出异常,避免返回null引发空指针
内容的提问来源于stack exchange,提问作者C. B. Prosser
相关产品推荐
相关产品推荐

