如何用泛型声明键为Class<X>值为对应JsonSerializer<X>的Map
问题根源
你声明的Map<Class<?>, ? extends JsonSerializer<?>>虽然逻辑上符合预期,但编译器无法将键的Class<?>和值的JsonSerializer<?>关联起来——这两个通配符是独立的,编译器无法确认某一项的键和值对应的是同一个泛型类型T。而SimpleModule.addSerializer的方法签名是<T> void addSerializer(Class<T> type, JsonSerializer<? super T> serializer),它要求传入的Class和Serializer必须属于同一个T(或Serializer是T的父类型),所以独立的通配符会导致类型不匹配错误。
解决方案
方案一:使用泛型方法捕获类型(最直接)
先调整CsvWriterConfig中的Map定义,去掉值的通配符限制(因为addSerializer接受JsonSerializer<? super T>,JsonSerializer<?>可以兼容):
public class CsvWriterConfig { private Map<Class<?>, JsonSerializer<?>> serializers; // getter、setter方法 public Map<Class<?>, JsonSerializer<?>> getSerializers() { return serializers; } public void setSerializers(Map<Class<?>, JsonSerializer<?>> serializers) { this.serializers = serializers; } }
然后编写一个泛型辅助方法,帮编译器捕获统一的T类型,消除类型错误:
// 泛型方法,明确绑定Class<T>和JsonSerializer<? super T>的关联 private <T> void addSerializer(SimpleModule module, Class<T> type, JsonSerializer<? super T> serializer) { module.addSerializer(type, serializer); } // 遍历添加时调用该方法 SimpleModule module = new SimpleModule(); for (Map.Entry<Class<?>, JsonSerializer<?>> entry : config.getSerializers().entrySet()) { // 这里需要unchecked转换,确保运行时类型正确即可 @SuppressWarnings("unchecked") Class<T> type = (Class<T>) entry.getKey(); addSerializer(module, type, entry.getValue()); }
方案二:用泛型Holder类实现类型安全(最严谨)
如果想要完全避免unchecked警告,可以创建一个泛型Holder类,显式绑定Class<T>和对应的JsonSerializer:
// 泛型Holder,绑定类型和序列化器 public class SerializerHolder<T> { private final Class<T> type; private final JsonSerializer<? super T> serializer; public SerializerHolder(Class<T> type, JsonSerializer<? super T> serializer) { this.type = type; this.serializer = serializer; } // getter方法 public Class<T> getType() { return type; } public JsonSerializer<? super T> getSerializer() { return serializer; } }
修改CsvWriterConfig的字段为Holder的集合:
public class CsvWriterConfig { private List<SerializerHolder<?>> serializers; // getter、setter方法 public List<SerializerHolder<?>> getSerializers() { return serializers; } public void setSerializers(List<SerializerHolder<?>> serializers) { this.serializers = serializers; } }
遍历添加时完全不需要类型转换,编译期即可保证类型安全:
SimpleModule module = new SimpleModule(); for (SerializerHolder<?> holder : config.getSerializers()) { addSerializer(module, holder.getType(), holder.getSerializer()); } // 复用之前的泛型辅助方法 private <T> void addSerializer(SimpleModule module, Class<T> type, JsonSerializer<? super T> serializer) { module.addSerializer(type, serializer); }
方案三:直接unchecked转换(最简但需谨慎)
如果不想修改Map结构,也可以直接在遍历的时候强制转换,同时添加@SuppressWarnings注解消除警告,但要确保Map中存入的键值对确实是一一对应的类型:
SimpleModule module = new SimpleModule(); for (Map.Entry<Class<?>, ? extends JsonSerializer<?>> entry : config.getSerializers().entrySet()) { @SuppressWarnings("unchecked") Class<Object> type = (Class<Object>) entry.getKey(); @SuppressWarnings("unchecked") JsonSerializer<Object> serializer = (JsonSerializer<Object>) entry.getValue(); module.addSerializer(type, serializer); }
总结
方案一兼顾简洁性和类型安全(仅一处可控的unchecked转换),是最常用的解决方案;方案二完全消除unchecked警告,适合对类型严谨性要求高的场景;方案三最简但依赖开发者保证数据正确性,需谨慎使用。
内容的提问来源于stack exchange,提问作者Paul Marcelin Bejan

