泛型擦除机制及相关问题

泛型擦除机制及相关问题泛型的擦除机制 桥方法 深层泛型处理 强转

一、类型擦除

public class A<T> { 
    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; } } 

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

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

二、类型擦除时机

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

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

三、类型检查时机

@Test public void test3() { 
    ArrayList<String> arrayList=new ArrayList<String>(); arrayList.add("123"); arrayList.add(123);//编译错误 } 

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

@Test public void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 
    ArrayList<Integer> arrayList=new ArrayList<>(); //这样调用add方法只能存储整形,因为泛型类型的实例为Integer arrayList.add(1); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "asd"); for (int i=0;i<arrayList.size();i++) { 
    System.out.println(arrayList.get(i)); } } //打印,反射跳过了检查部分 1 asd 

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

@Test public void test3() { 
    ArrayList arrayList1=new ArrayList<String>(); arrayList1.add("ad"); arrayList1.add(1);//编译告警:Unchecked call to add(E) ArrayList<String> 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<String> { 
    @Override public void setValue(String value) { 
    } } 
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<AlUsers> usersList = Lists.newArrayList(alUsers1, alUsers2); //设计的多泛型嵌套map Map<String, List<AlUsers>> map = Maps.newHashMap(); map.put("L", usersList); //序列化 String json = mapper.writeValueAsString(map); //反序列化处理 Map<String, List<AlUsers>> map1 = mapper.readValue(json, Map.class); List<AlUsers> 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<String, List<AlUsers>> map1 = mapper.readValue(json, new TypeReference<Map<String, List<AlUsers>>>(){ 
   }); List<AlUsers> 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子类都可以使用这个方法进行处理

 //方法调用测试 @Test public void testForceConvert() { 
    List<Lenovo> goods = Lists.newArrayList( new Lenovo(1L, "apple", "12000", "16G", "1T"), new Lenovo(2L, "Huawei", "8000", "32G", "2T") ); List<T> result = dealGoodList((List<T>) goods); System.out.println(JackSonUtil.objToJson(result)); } //通用方法,主要作用就是将价钱增加人民币符号 private List<T> dealGoodList(List<T> 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"}] 
private List<T> dealGoodList(List<T> 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"}] 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/177049.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午8:26
下一篇 2026年3月26日 下午8:26


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号