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

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

  •  
  •   abcbuzhiming · 2018-10-18 16:40:10 +08:00 · 11203 次点击
    这是一个创建于 2238 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我知道有泛型擦除,但是我不知道该如何用泛型擦除来解释这个现象

    在被一个序列化工具坑过后,经过测试,发现了一个我以前没注意到的 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 条附言  ·  2018-10-18 17:55:47 +08:00
    补充说明一下,貌似泛型擦除问题是没法绕过的,我的这个方式

    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
    hongch
        1
    hongch  
       2018-10-18 16:45:28 +08:00
    没有用过 jackson,不出意外你传入一个 List<T>或者一个 T.class 最后都会解析成一个 type,可以看看他的源码最终需要的是什么直接传进去就好了
    abcbuzhiming
        2
    abcbuzhiming  
    OP
       2018-10-18 16:53:25 +08:00
    @hongch 要是这么容易我就不会提出来说了,核心问题是 Java 的泛型为什么没法处理容器内带有类型限定的这种情况
    blindpirate
        3
    blindpirate  
       2018-10-18 16:55:42 +08:00   ❤️ 6
    因为不存在 List<ClassName>.class,只有 List.class。至于为什么,需要从历史上讲起: https://www.zhihu.com/question/28665443
    BBCCBB
        4
    BBCCBB  
       2018-10-18 17:02:44 +08:00
    这个类还有一个用 JavaType 作参数的构造函数, 用这个
    yidinghe
        5
    yidinghe  
       2018-10-18 17:03:40 +08:00
    容器泛型在运行时是被抹掉的,所以对于一个 List 对象,你在运行时并不能获取它针对哪种泛型。
    gam2046
        6
    gam2046  
       2018-10-18 17:04:29 +08:00
    Java 的泛型是基于类型擦除 + 强制类型转换实现的。因此泛型的实际类型,在运行时期已经被抹掉了。无法返回其 Class。因此不允许这样的语法,没有人知道泛型的实际类型。
    BBCCBB
        7
    BBCCBB  
       2018-10-18 17:04:37 +08:00
    TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, MyBean.class);

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

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

    这样生成的 serializer 仍然是泛型化的.
    gaius
        10
    gaius  
       2018-10-18 17:41:05 +08:00
    new TypeRefernece<List<SomeClass>>(){}
    choice4
        11
    choice4  
       2018-10-18 17:48:22 +08:00
    运行时泛型是 Object
    abcbuzhiming
        12
    abcbuzhiming  
    OP
       2018-10-18 17:57:49 +08:00
    @xingda920813 朋友,我楼顶补充说明一下,你这个方法也是没效的,仅仅不报错了,而且很奇葩的是,转换得到的其实是 List<LinkHashMap>类型,这个类型赋值给 List<ClassName>的时候,系统是不报错的,但是,如果你从最后的 List<ClassName>中取出一个对象,执行这个对象的方法的时候,就报错了,告诉你类型转换不能
    gam2046
        13
    gam2046  
       2018-10-18 18:14:58 +08:00
    @abcbuzhiming 类型擦除后,基于多态,就是 Object。所以真正的类型就没了。
    lovedebug
        14
    lovedebug  
       2018-10-18 18:17:23 +08:00 via Android
    因为类型擦除发生在编译阶段,导致所有 list<t >都变成了 list,对应 class 都是 list.class。
    TommyLemon
        15
    TommyLemon  
       2018-10-18 18:27:09 +08:00
    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];
    }
    ```
    MetoYou
        16
    MetoYou  
       2018-10-18 19:35:59 +08:00
    最近也遇到了这个问题,绕不过去的,java 机制的问题。
    Cbdy
        17
    Cbdy  
       2018-10-18 19:40:58 +08:00 via Android
    Type reference 了解一下
    Parameterized type 了解一下
    beginor
        18
    beginor  
       2018-10-18 20:42:07 +08:00 via Android
    因为 Java 的泛型是假的
    micean
        19
    micean  
       2018-10-18 20:56:48 +08:00
    可以从字节码的层面去看
    aristotll
        20
    aristotll  
       2018-10-18 21:29:27 +08:00
    effective java 有讲
    ddup
        21
    ddup  
       2018-10-18 21:34:56 +08:00
    没办法,因为运行时里没有泛型,缺少必要的类型信息,只能如此。
    反正既然是 Java 就不要介意美丑的问题。
    leeg810312
        22
    leeg810312  
       2018-10-18 23:37:06 +08:00 via Android
    Java 早期设计偷懒埋下的巨坑,一味要求兼容性,以致于无法做到运行时的泛型支持,没有办法反射获取泛型类型信息,一直到现在 Java11 了还是没有解决,哪有永久的兼容性啊?
    deming
        23
    deming  
       2018-10-19 09:58:29 +08:00   ❤️ 1
    可以用这个方法:

    class CustomerInfoList extends List<CustomerInfoDto> {};

    然后就可以使用:

    Jackson2JsonRedisSerializer<CustomerInfoList> serializer = ... 来实现。
    q397064399
        24
    q397064399  
       2018-10-19 10:18:30 +08:00
    List<List<List<List>>>> 在 Java 看来就是 List<Object> 就对了 :doge 这个算是历史遗留了
    vincenteof
        25
    vincenteof  
       2018-10-19 10:57:17 +08:00
    运行时和编译期的区别啊,编译后并没有你写的那个东西
    xuanbg
        26
    xuanbg  
       2018-10-20 18:33:14 +08:00
    3 楼正解
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3532 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 10:45 · PVG 18:45 · LAX 02:45 · JFK 05:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.