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

2018-10-18 16:40:10 +08:00
 abcbuzhiming
我知道有泛型擦除,但是我不知道该如何用泛型擦除来解释这个现象

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

可能是因为你对 jackson 不熟.
abcbuzhiming
2018-10-18 17:14:37 +08:00
@yidinghe
@gam2046 有个问题,这个“抹掉”到底是指的完全不在了,还是被转换成特定的类型了,如果是转换成特定的类型了,我应该还是能拿到啊?
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
2018-10-18 17:41:05 +08:00
new TypeRefernece<List<SomeClass>>(){}
choice4
2018-10-18 17:48:22 +08:00
运行时泛型是 Object
abcbuzhiming
2018-10-18 17:57:49 +08:00
@xingda920813 朋友,我楼顶补充说明一下,你这个方法也是没效的,仅仅不报错了,而且很奇葩的是,转换得到的其实是 List<LinkHashMap>类型,这个类型赋值给 List<ClassName>的时候,系统是不报错的,但是,如果你从最后的 List<ClassName>中取出一个对象,执行这个对象的方法的时候,就报错了,告诉你类型转换不能
gam2046
2018-10-18 18:14:58 +08:00
@abcbuzhiming 类型擦除后,基于多态,就是 Object。所以真正的类型就没了。
lovedebug
2018-10-18 18:17:23 +08:00
因为类型擦除发生在编译阶段,导致所有 list<t >都变成了 list,对应 class 都是 list.class。
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
2018-10-18 19:35:59 +08:00
最近也遇到了这个问题,绕不过去的,java 机制的问题。
Cbdy
2018-10-18 19:40:58 +08:00
Type reference 了解一下
Parameterized type 了解一下
beginor
2018-10-18 20:42:07 +08:00
因为 Java 的泛型是假的
micean
2018-10-18 20:56:48 +08:00
可以从字节码的层面去看
aristotll
2018-10-18 21:29:27 +08:00
effective java 有讲

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/498842

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX