博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
泛型擦除机制及相关问题
阅读量:3963 次
发布时间:2019-05-24

本文共 6859 字,大约阅读时间需要 22 分钟。

泛型擦除机制

一、类型擦除

public class A
{
private T value; public T getValue() {
return value; } public void setValue(T value) {
this.value = value; }}//编译后public class A{
private Object value; public Object getValue() {
return value; } public void setValue(Object value) {
this.value = value; }}

所以,运行时进行类型查询的时候使用下面的方法是错误的

@Testpublic void test3() {
ArrayList
arrayList=new ArrayList<>(); if (arrayList instanceof ArrayList
) {
//illegal generic type instanceof System.out.println("ture"); }}

二、类型擦除时机

@Testpublic void test1() {
A
stringA = new A<>(); A
integerA = new A<>(); System.out.println(stringA.getClass()==integerA.getClass());}//打印true

说明泛型的擦除是在编译前,jvm会将其看做同一个普通类Object进行处理

三、类型检查时机

@Testpublic void test3() {
ArrayList
arrayList=new ArrayList
(); arrayList.add("123"); arrayList.add(123);//编译错误}

先检查泛型类型,再擦除,再编译,保证我们只能使用泛型变量限定的类型进行操作。

@Testpublic void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList
arrayList=new ArrayList<>(); //这样调用add方法只能存储整形,因为泛型类型的实例为Integer arrayList.add(1); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "asd"); for (int i=0;i

那么,这么类型检查是针对谁的呢?

@Testpublic void test3() {
ArrayList arrayList1=new ArrayList
(); arrayList1.add("ad"); arrayList1.add(1);//编译告警:Unchecked call to add(E) ArrayList
arrayList2=new ArrayList<>(); arrayList2.add("ad"); arrayList2.add(1);//编译报错}

new ArrayList() 只是在内存中开辟一个存储空间,可以存储任何的类型对象。

真正涉及类型检查的是它的引用,arrayList2指定了String泛型,从而完成了泛型类的检查,因此只能添加String对象。

可以看出,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象

四、桥方法

问题在于类型擦除与多态发生了冲突。要解决这个问题, 就需要编译器在类中生成一个桥方法(Bridge Method)——《Java核心技术卷I》

新写了一个 B 类,继承自 A 类,并重写A 类的 setValue 方法

public class B extends A
{
@Override public void setValue(String value) {
}}

但问题出来了,我们发现 B 类中的 setValue 方法参数与 A 类中的 setValue 方法参数不一样。按照 Java 重写方法的规则,B 类中的 setValue 方法实际上并没有重写父类中的方法,而是重载。也就是说我们的意图是重写,而实际上却是重载,类型擦除与多态发生了冲突。

Java编译器会在 B 类中自动生成一个桥方法(bridge method)

class B extends A {
public void setValue(String value) {
this.value = value; } //A的方法 public void setValue(Object obj) {
//调用B的方法 setValue((String)obj); }}

可以看到子类B中有两个setValue方法,一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型的就是 Java 编译器帮我们生成的桥方法,桥方法内部其实就是调用了子类新定义的 setValue 方法,这样就避免了子类重写了父类的方法后还能调用到父类的方法。

同样的,如果给B类添加一个getValue方法,编译器也会自动生成一个桥方法

public class B extends A {
public String getValue(){
...} //编译器自动生成的桥方法 public Object getValue(){
return getValue(); }}

在Java语法中,这是不合法的,我们定义了方法名和参数列表都一样的两个方法,这违背了重载的要求。但编译器可以产生两个仅返回类型不同的方法的字节码,虚拟机能够正确地处理这一情况的。

五、多层泛型嵌套导致的反序列化问题

//调用jackson对象ObjectMapper mapper = new ObjectMapper();mapper.setSerializationInclusion(Include.NON_NULL); //封装用户对象AlUsers alUsers1 = new AlUsers();alUsers1.setUsername("WOW");AlUsers alUsers2 = new AlUsers();alUsers2.setUsername("SC2");List
usersList = Lists.newArrayList(alUsers1, alUsers2);//设计的多泛型嵌套mapMap
> map = Maps.newHashMap();map.put("L", usersList);//序列化String json = mapper.writeValueAsString(map);//反序列化处理Map
> map1 = mapper.readValue(json, Map.class);List
users = map1.get("L");for (AlUsers user : users) {
System.out.println(mapper.writeValueAsString(user));}

结果报出异常

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.flight.carryprice.entity.AlUsers

Jackson是不支持多泛型深度嵌套反序列化的,强转后的结果就是默认将AlUsers对象变成了LinkedHashMap,所以在循环的时候强转AlUsers对象就会报错

我们可以使用***TypeReference***解决这个问题

//源码 protected TypeReference() {
Type superClass = getClass().getGenericSuperclass(); if (superClass instanceof Class
) {
// sanity check, should never happen throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information"); } /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect * it is possible to make it fail? * But let's deal with specific * case when we know an actual use case, and thereby suitable * workarounds for valid case(s) and/or error to throw * on invalid one(s). */ _type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; }

TypeReference对象就是通过java反省机制,获取所指定的泛型的类型,在反序列中根据指定的泛型类型进行反序列化,实现对多层嵌套泛型的处理

//通过TypeReference指定反序列化的类型Map
> map1 = mapper.readValue(json, new TypeReference
>>(){
});List
users = map1.get("L");for (AlUsers user : users) {
System.out.println(mapper.writeValueAsString(user));}//打印{
"username":"WOW"}{
"username":"SC2"}

六、泛型强转优于反序列化

我们定义一个Goods父类和一个Lenovo子类

public class Goods {
/** * 商品id */ private Long goodId; /** * 商品名称 */ private String goodName; /** * 商品价格 */ private String goodPrice;}public class Lenovo extends Goods {
/** * 内存容量 */ private String memory; /** * 硬盘容量 */ private String solidDisk; /** * 有参构造 */ public Lenovo(Long goodId, String goodName, String goodPrice, String memory, String solidDisk) {
super(goodId, goodName, goodPrice); this.memory = memory; this.solidDisk = solidDisk; }}

我们模拟一个Lenovo集合,然后对这个集合进行操作处理,这个方法的入参和出参我们都设置为泛型,这样可以具备通用性,其他的Goods子类都可以使用这个方法进行处理

//方法调用测试@Testpublic void testForceConvert() {
List
goods = Lists.newArrayList( new Lenovo(1L, "apple", "12000", "16G", "1T"), new Lenovo(2L, "Huawei", "8000", "32G", "2T") ); List
result = dealGoodList((List
) goods); System.out.println(JackSonUtil.objToJson(result));}//通用方法,主要作用就是将价钱增加人民币符号private List
dealGoodList(List
goodsList) {
goodsList.forEach(e -> {
//我们使用反序列化将其转为父类,然后操作数据 Goods goods = JackSonUtil.objToobj(e, Goods.class); goods.setGoodPrice(goods.getGoodPrice() + "¥"); }); return goodsList;}//打印结果[{
"goodId":1,"goodName":"apple","goodPrice":"12000","memory":"16G","solidDisk":"1T"},{
"goodId":2,"goodName":"Huawei","goodPrice":"8000","memory":"32G","solidDisk":"2T"}]

我们会发现,并没有发生任何变化,主要是因为反序列的时候,起本质还是在堆内新开辟了一块区域,参考文章

这个时候,强转泛型要优于反序列化,因为强转,使用的是一块堆,此外,这个强转的父类也只是一个样式,其本质还是Lenovo

private List
dealGoodList(List
goodsList) {
goodsList.forEach(e -> {
Goods goods = (Goods) e; goods.setGoodPrice(goods.getGoodPrice() + "¥"); }); return goodsList; }//打印结果[{
"goodId":1,"goodName":"apple","goodPrice":"12000¥","memory":"16G","solidDisk":"1T"},{
"goodId":2,"goodName":"Huawei","goodPrice":"8000¥","memory":"32G","solidDisk":"2T"}]

转载地址:http://stgzi.baihongyu.com/

你可能感兴趣的文章
Hibernate性能优化
查看>>
Spring核心ioc
查看>>
SSH框架总结(框架分析+环境搭建+实例源码下载)
查看>>
Struts2+Spring3+Mybatis3开发环境搭建
查看>>
mongoDB入门必读(概念与实战并重)
查看>>
通俗易懂解剖jbpm4
查看>>
rsync
查看>>
makefile
查看>>
linux 文件权限
查看>>
一些比较好的golang安全项目
查看>>
HTTP状态码
查看>>
go语言
查看>>
mysql mariaDB 以及存储引擎
查看>>
游戏行业了解介绍
查看>>
linux at 命令使用
查看>>
Go在windows下执行命令行指令
查看>>
inotify
查看>>
inode
查看>>
Shell: sh,bash,csh,tcsh等shell的区别
查看>>
golang ubuntu 配置 笔记
查看>>