我知道有泛型擦除,但是我不知道该如何用泛型擦除来解释这个现象
在被一个序列化工具坑过后,经过测试,发现了一个我以前没注意到的 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;只能像上面那种迂回手段那样建哥对象让后 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 传入,所以目前没办法
可参考评论
#3
blindpirate Thu, 31 Oct 2018 16:55:42
因为不存在 List<ClassName>.class
,只有 List.class。至于为什么,需要从历史上讲起: https://www.zhihu.com/question/28665443
#7
BBCCBB Thu, 31 Oct 2018 17:04:37
TypeFactory.defaultInstance().constructCollectionType(ArrayList.class, MyBean.class);
可能是因为你对 jackson 不熟.
#9
xingda920813 Thu, 31 Oct 2018 17:22:34
@abcbuzhiming 抛开本例中的 Jackson 不谈 (因为有其他的 API, 如上 @BBCCBB 回答的), 只论纯 Java 的话.
可以这样用: Jackson2JsonRedisSerializer<List<SomeClass>> serializer = (Jackson2JsonRedisSerializer<List<SomeClass>>) new Jackson2JsonRedisSerializer<>((Class<?>) List.class);
这样生成的 serializer 仍然是泛型化的.
#10
gaius Thu, 31 Oct 2018 17:41:05
new TypeRefernece<List>(){}
#12
abcbuzhiming Thu, 31 Oct 2018 17:57:49
@xingda920813 朋友,我楼顶补充说明一下,你这个方法也是没效的,仅仅不报错了,而且很奇葩的是,转换得到的其实是 List类型,这个类型赋值给 List的时候,系统是不报错的,但是,如果你从最后的 List中取出一个对象,执行这个对象的方法的时候,就报错了,告诉你类型转换不能
#14
lovedebug Thu, 31 Oct 2018 18:17:23
因为类型擦除发生在编译阶段,导致所有 list都变成了 list,对应 class 都是 list.class。
#15
TommyLemon Thu, 31 Oct 2018 18:27:09
Gson 也差不多:
List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());
FastJSON 很方便:
List<Person> people = JSON.parseArray(jsonData, Person.class);
不支持 Class.class 就是因为 Java 为了兼容 1.4 及以下的 JVM,
实现时用的是 泛型擦除 方式,只能在运行前静态检查类型,
编译通过后 Type 就换成 Object 了,只能强转获取。
这个你看下 ArrayList 的源码就知道了,
里面是用 Object[] elementData 来存列表数据的,
get(int position) 内 return 的时候强转:
public E get(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
return (E) elementData[index];
}