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

为什么 Java 不允许 List<ClassName>.class 这样的操作?

  •  
  •   abcbuzhiming · 29 天前 · 2335 次点击
    我知道有泛型擦除,但是我不知道该如何用泛型擦除来解释这个现象

    在被一个序列化工具坑过后,经过测试,发现了一个我以前没注意到的 Java 特性盲点:

    Java 允许这样操作:
    List.class
    但是他不允许这样的操作:
    List<ClassName>.class
    所以如果你有一个泛型方法需要传入 List<ClassName>的 Class<T>类型的时候,用如下方式是错的
    Jackson2JsonRedisSerializer<List<ClassName>> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<List<ClassName>>(List<ClassName>.class);

    只能用如下丑陋的方式
    Jackson2JsonRedisSerializer<List> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<List.class>(List.class);
    于是,你想限定 List 容器里装什么,对不起,你限制不了,而且这玩意还会给你个警告,根据放狗的结果,不上压制警告注解的注解没法解决

    然后我又够了好久,才发现还有一个迂回战术,能让你对 List 容器内部的类型进行限制,写法是下面这样的
    Jackson2JsonRedisSerializer<List<CustomerInfoDto>> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<List<CustomerInfoDto>>(
    (Class<List<CustomerInfoDto>>) new ArrayList<CustomerInfoDto>().getClass());
    新建一个容器的实现类泛型容器,然后调它的 getClass 方法。。。
    虽然它也有一个必须用注解压制的警告,但是好歹能工作啊。

    我自己查阅了不是关于 Java class 属性的资料,只能发现这玩意是编译后就被定义的属性,来自虚拟机本身,但是没有解释为什么 List<ClassName>.class 是不允许的。
    所以,我的问题就是如何解释这个现象,其次就是如果我要拿到类似 List<ClassName>这样的类型的 Class<T>;只能像上面那种迂回手段那样建哥对象让后 getClass 吗?
    第 1 条附言  ·  29 天前
    补充说明一下,貌似泛型擦除问题是没法绕过的,我的这个方式

    Jackson2JsonRedisSerializer<List<ClassName>> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<List<ClassName>>(
    (Class<List<ClassName>>) new ArrayList<ClassName>().getClass());

    和楼下一位朋友的方式
    Jackson2JsonRedisSerializer<List<ClassName>> jackson2JsonRedisSerializer = (Jackson2JsonRedisSerializer<List<ClassName>>) new Jackson2JsonRedisSerializer<>(
    (Class<?>) List.class);

    仅仅是能编译通过,但是,最后转回并得到的仍然不是我想要的 List<ClassName> 泛型类型的对象,很奇葩的是,转换过程并不报错,但是,当你从 List<ClassName>中取出一个对象,运行 clasName.getClass()的时候,就报错 java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.github.domain.ClassName。
    确实像某些人说的那样,如果要借助 Jackson 的序列化器反序列化容器对象,必须借助 Typereference,但是 Jackson2JsonRedisSerializer 本身不接受 Typereference 传入,所以目前没办法
    26 回复  |  直到 2018-10-20 18:33:14 +08:00
        1
    hongch   29 天前
    没有用过 jackson,不出意外你传入一个 List<T>或者一个 T.class 最后都会解析成一个 type,可以看看他的源码最终需要的是什么直接传进去就好了
        2
    abcbuzhiming   29 天前
    @hongch 要是这么容易我就不会提出来说了,核心问题是 Java 的泛型为什么没法处理容器内带有类型限定的这种情况
        3
    blindpirate   29 天前   ♥ 6
    因为不存在 List<ClassName>.class,只有 List.class。至于为什么,需要从历史上讲起: https://www.zhihu.com/question/28665443
        4
    BBCCBB   29 天前
    这个类还有一个用 JavaType 作参数的构造函数, 用这个
        5
    yidinghe   29 天前
    容器泛型在运行时是被抹掉的,所以对于一个 List 对象,你在运行时并不能获取它针对哪种泛型。
        6
    gam2046   29 天前
    Java 的泛型是基于类型擦除 + 强制类型转换实现的。因此泛型的实际类型,在运行时期已经被抹掉了。无法返回其 Class。因此不允许这样的语法,没有人知道泛型的实际类型。
        7
    BBCCBB   29 天前
    TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, MyBean.class);

    可能是因为你对 jackson 不熟.
        8
    abcbuzhiming   29 天前
    @yidinghe
    @gam2046 有个问题,这个“抹掉”到底是指的完全不在了,还是被转换成特定的类型了,如果是转换成特定的类型了,我应该还是能拿到啊?
        9
    xingda920813   29 天前
    @abcbuzhiming 抛开本例中的 Jackson 不谈 (因为有其他的 API, 如上 @BBCCBB 回答的), 只论纯 Java 的话.

    可以这样用: Jackson2JsonRedisSerializer<List<SomeClass>> serializer = (Jackson2JsonRedisSerializer<List<SomeClass>>) new Jackson2JsonRedisSerializer<>((Class<?>) List.class);

    这样生成的 serializer 仍然是泛型化的.
        10
    gaius   29 天前
    new TypeRefernece<List<SomeClass>>(){}
        11
    choice4   29 天前
    运行时泛型是 Object
        12
    abcbuzhiming   29 天前
    @xingda920813 朋友,我楼顶补充说明一下,你这个方法也是没效的,仅仅不报错了,而且很奇葩的是,转换得到的其实是 List<LinkHashMap>类型,这个类型赋值给 List<ClassName>的时候,系统是不报错的,但是,如果你从最后的 List<ClassName>中取出一个对象,执行这个对象的方法的时候,就报错了,告诉你类型转换不能
        13
    gam2046   29 天前
    @abcbuzhiming 类型擦除后,基于多态,就是 Object。所以真正的类型就没了。
        14
    lovedebug   29 天前 via Android
    因为类型擦除发生在编译阶段,导致所有 list<t >都变成了 list,对应 class 都是 list.class。
        15
    TommyLemon   29 天前
    Gson 也差不多:
    ```java
    List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());
    ```

    FastJSON 很方便:
    ```java
    List<Person> people = JSON.parseArray(jsonData, Person.class);
    ```

    不支持 Class<Type>.class 就是因为 Java 为了兼容 1.4 及以下的 JVM,
    实现时用的是 泛型擦除 方式,只能在运行前静态检查类型,
    编译通过后 Type 就换成 Object 了,只能强转获取。
    这个你看下 ArrayList 的源码就知道了,
    里面是用 Object[] elementData 来存列表数据的,
    get(int position) 内 return 的时候强转:
    ```java
    public E get(int index) {
    if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    return (E) elementData[index];
    }
    ```
        16
    MetoYou   29 天前
    最近也遇到了这个问题,绕不过去的,java 机制的问题。
        17
    Cbdy   29 天前 via Android
    Type reference 了解一下
    Parameterized type 了解一下
        18
    beginor   29 天前 via Android
    因为 Java 的泛型是假的
        19
    micean   29 天前
    可以从字节码的层面去看
        20
    aristotll   29 天前
    effective java 有讲
        21
    ddup   29 天前
    没办法,因为运行时里没有泛型,缺少必要的类型信息,只能如此。
    反正既然是 Java 就不要介意美丑的问题。
        22
    leeg810312   29 天前 via Android
    Java 早期设计偷懒埋下的巨坑,一味要求兼容性,以致于无法做到运行时的泛型支持,没有办法反射获取泛型类型信息,一直到现在 Java11 了还是没有解决,哪有永久的兼容性啊?
        23
    deming   29 天前
    可以用这个方法:

    class CustomerInfoList extends List<CustomerInfoDto> {};

    然后就可以使用:

    Jackson2JsonRedisSerializer<CustomerInfoList> serializer = ... 来实现。
        24
    q397064399   29 天前
    List<List<List<List>>>> 在 Java 看来就是 List<Object> 就对了 :doge 这个算是历史遗留了
        25
    vincenteof   29 天前
    运行时和编译期的区别啊,编译后并没有你写的那个东西
        26
    xuanbg   27 天前
    3 楼正解
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2016 人在线   最高记录 3821   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 18ms · UTC 03:00 · PVG 11:00 · LAX 19:00 · JFK 22:00
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1