本文共 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() { ArrayListarrayList=new ArrayList<>(); if (arrayList instanceof ArrayList ) { //illegal generic type instanceof System.out.println("ture"); }}
@Testpublic void test1() { AstringA = new A<>(); A integerA = new A<>(); System.out.println(stringA.getClass()==integerA.getClass());}//打印true
说明泛型的擦除是在编译前,jvm会将其看做同一个普通类Object进行处理
@Testpublic void test3() { ArrayListarrayList=new ArrayList (); arrayList.add("123"); arrayList.add(123);//编译错误}
先检查泛型类型,再擦除,再编译,保证我们只能使用泛型变量限定的类型进行操作。
@Testpublic void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { ArrayListarrayList=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");ListusersList = 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
我们定义一个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() { Listgoods = 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 ListdealGoodList(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/