V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
puremaker
V2EX  ›  Java

学习 Jackson 封装时候遇到一个泛型问题希望大牛能帮忙解惑

  •  
  •   puremaker · 22 天前 · 1572 次点击

    封装一个转指定实体类 list 的方法,有如下两种

    public static <T> List<T> parseList(String jsonString, Class<T> elementClazz) throws Exception {
    	ObjectMapper mapper = new ObjectMapper();
        TypeReference<List<T>> typeReference = new TypeReference<List<T>>() {};
        return mapper.readValue(jsonString, typeReference);
    }
    
    public static <T> List<T> parseList(String jsonString, TypeReference<List<T>> typeReference) throws Exception {
    	ObjectMapper mapper = new ObjectMapper();
    	return mapper.readValue(jsonString, typeReference);
    }
    
    public static void main(String[] args) throws  Exception {
            String jsonListStr = "[{\"username\":\"pure1\",\"phone\":\"18xxx\"},{\"username\":\"pure2\",\"phone\":\"19xxx\"}]";
            List<User> userList1 = parseList(jsonListStr, User.class);
            List<User> userList2 = parseList(jsonListStr, new TypeReference<List<User>>(){});
    }
    

    两个转完的 list 里,第一个 list 里的对象在断点里看实际上是个LinkedHashMap ,是无法正常调用实体类的 get 方法的。第二个 list 里的对象就是真正的User。所以我分别去看了两个方法的 typeReference 对象,第一个方法的 typeReference 对象里的_type 值为“java.util.List<T>”,第二个方法里的 typeReference 对象里的_type 值为“java.util.List<xxx.xxx.entity.User>”。虽然第二个方法可以正常使用,但是封装肯定是为了简便,以TypeReference<List<T>> typeReference作为入参感觉很奇怪,我底层了解的不多,我的认知里在入参的时候 new 一个 TypeReference 和在方法里 new 一个 TypeReference 应该是一样的才对。希望有大牛帮我解惑,或者是不是我第一个方法的代码写的有问题。

    在此先谢谢各位了!!!

    16 条回复    2025-08-17 15:24:11 +08:00
    xtreme1
        1
    xtreme1  
       22 天前
    JavaType listType = mapper.getTypeFactory().constructCollectionType(List.class, elementClazz);
    return mapper.readValue(jsonString, listType);

    第一个 T 运行时被擦除了, 单步一下 TypeReference 的构造方法就知道了.
    visper
        2
    visper  
       22 天前
    看起来像是 java 的类型擦除然后第一个方法在里面 new 的时候,里面无法再知道那个是什么类型。
    raylax7
        3
    raylax7  
       22 天前
    第一种会类型擦除
    第二种 new TypeReference<List<User>>() {} 会生成一个单独的类文件 Class$1.class
    class Class$1 extends TypeReference<java.util.List<com.example.User>> { } 类型签名保留了 List<User> 的完整信息
    调用 getClass().getGenericSuperclass(),就能拿到这个签名,然后解析出 User
    dcsuibian
        4
    dcsuibian  
       22 天前
    Java 在编译时会进行类型擦除,大多数会被擦掉。但继承结构中的一部分泛型信息会保存,可以通过反射读到。
    lemondev
        5
    lemondev  
       22 天前   ❤️ 1
    第一个 parseList 方法有问题,虽然 typeReference 是 TypeReference<List<T>>,
    但是在运行时,T 已经被擦除成 Object 。Jackson 是不知道的,他得到了一个 Object 。具体你可以看编译后的字节码。可以看出来端倪。

    这种情况你必须手动构造泛型类型了。

    public static <T> List<T> parseList(String jsonString, Class<T> elementClazz) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    JavaType javaType = mapper.getTypeFactory()
    .constructCollectionType(List.class, elementClazz);
    return mapper.readValue(jsonString, javaType);
    }
    hapeman
        6
    hapeman  
       22 天前
    java 的泛型是假泛型,只在编译期有效,编译完成后会所有泛型都会被擦除
    puremaker
        7
    puremaker  
    OP
       22 天前
    @lemondev 非常通透,理解了,感谢感谢
    puremaker
        8
    puremaker  
    OP
       22 天前
    @hapeman 受教了
    puremaker
        9
    puremaker  
    OP
       22 天前
    @raylax7 大概能懂了
    xuanbg
        10
    xuanbg  
       22 天前
    我是这样写的:
    public static <T> List<T> toList(String json, Class<T> type) {
    try {
    return MAPPER.readValue(json.trim(), getJavaType(List.class, type));
    } catch (IOException ex) {
    throw new BusinessException(ex.getMessage());
    }
    }
    xuanbg
        11
    xuanbg  
       22 天前
    @xuanbg 实际上 getJavaType 方法就是 5 楼一样的代码
    looveh
        12
    looveh  
       22 天前
    最近刚解决这个问题就刷到你这个问题😐
    我是 Postgresql 查询的时候把主数据和子数据一次性查询,子数据使用 json 数组作为子数据的一列,然后自定义一个 TypeHandler 将 json 数组转成对象集合;

    ```java
    @MappedJdbcTypes(value = JdbcType.VARCHAR)
    public class CustomListTypeHandler<T> extends AbstractJsonTypeHandler<List<T>> {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final Class<T> elementType;
    public CustomListTypeHandler(Class<T> elementType) {
    this.elementType = elementType;
    }

    @Override
    protected List<T> parse(String json) {
    try {
    CollectionType collectionType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, elementType);
    return OBJECT_MAPPER.readValue(json, collectionType);
    } catch (JsonProcessingException e) {
    throw new RuntimeException(e);
    }
    }

    @Override
    protected String toJson(List<T> obj) {
    try {
    return OBJECT_MAPPER.writeValueAsString(obj);
    } catch (JsonProcessingException e) {
    throw new RuntimeException(e);
    }
    }
    }
    ```
    looveh
        13
    looveh  
       22 天前
    @looveh 话说评论怎么嵌入代码?不支持 md 么
    pointerman
        14
    pointerman  
       22 天前   ❤️ 1
    你这样写,每次调用方法都要 new 一个 ObjectMapper ,应该把 ObjectMapper 注入 Spring 容器,然后在工具类中 @Autowired 一个静态的 objectMapper 对象,所有工具类里的所有方法都用这个对象
    siweipancc
        15
    siweipancc  
       21 天前 via iPhone
    虽然大伙讨厌八股文,但是在这个场景八股文还是有点用的。
    shiloh595
        16
    shiloh595  
       17 天前 via Android
    🐮
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3353 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 04:52 · PVG 12:52 · LAX 21:52 · JFK 00:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.