Java中泛型的擦除和转换

本文深入探讨Java泛型的自动擦除与手动擦除机制,解析编译器如何处理泛型类,并通过实例说明了不当使用泛型可能导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

泛型的擦除和转换:
1. 泛型的擦除

  • 泛型编译后就消失了(泛型自动擦除):如下
    创建一个点类,具有x和y坐标的属性,但我们不知道坐标具体是整型还是浮点型还是字符串,所以我们就要泛型类来表示

    public class Point<T>
    {
    private T x;
    private T y;
    
    public T getX()
    {
    return this.x;
    }
    public void setX(T x) {
    this.x = x;
    }
    public T getY() {
    return this.y;
    }
    public void setY(T y) {
    this.y = y;
    }
    }

    新建一个Demo来演示Point的使用

    public class PointDemo {
    public static void main(String[] args) {
        Point<String> p = new Point<String>();
        String x = p.getX();
        System.out.println(x);
    
    }
    }

    反编译Demo后发现Java代码如下图
    反编译后的代码
    从中我们可以发现,编译器还是用强制类型转换为我们实现了泛型


  • 当把带有泛型的集合赋给不带泛型的集合时,此时泛型被擦除(手动擦除):如下图
    泛型的手动擦除

    list1集合虽然设定为Integer类型,但由于list2未设定类型(使用默认的Object类型),所以在list1赋值为list1的时候,先把list1中的类型擦除了,然后转换成Object


第二种类型的泛型转换并不是安全的,容易造成堆污染
如下图泛型转换引起的堆污染

//带有泛型的集合
        List<Integer> list1 = new ArrayList<>();
        list1.add(1);
        //不带泛型的集合
        List list2 = null;
        //将带泛型的集合赋值给不带泛型的集合
        list2 = list1;//此时泛型被擦除
        list2.add("AA");

        //带有String类型的泛型
        List<String> list3 = null;
        list3 = list2;
        list3.add("Hello");
        //Integer temp = list3.get(0);//用Integer接收list3中的第一个元素报错
        String temp = list3.get(0);
        //用String接收list3中的第一个元素,运行报错java.lang.Integer cannot be cast to java.lang.String

堆污染

### Java 擦除概念及原理 Java 中的是在编译期提供的一种语法糖,在运行时会被替换为原始类型,这一过程称为类型擦除。这意味着所有的参数都会被移除,并且任何具体的类型信息都将丢失[^2]。 #### 类型擦除的原因 由于 JDK 1.5 及更早版本并不支持,为了保持向后兼容性,引入了类型擦除的概念。如果不采用这种机制而选择为每一种具体类型实例生成独立的目标代码,则会显著增加程序体积以及占用更多内存资源。因此,通过类型擦除可以在不改变原有 JVM 架构的情况下实现对的支持[^3]。 #### 类型擦除的具体表现形式 当涉及到有边界限定的声明时,会有三种主要的情况: - **无界通配符** (`?`):此时不会有任何实际的变化发生; - **上界通配符** (`<? extends T>`):将会把所有操作视为该上限类型的操作; - **下界通配符** (`<? super T>`) :则视作最低限定了一个特定父类或接口的对象容器[^1]。 #### 实际应用中的影响 考虑如下例子来展示如何处理由类型擦除带来的挑战: ```java class Box<T> { private T content; public void setContent(T t){ this.content = t; } @SuppressWarnings("unchecked") // 需要显式抑制警告 public T getContent(){ return (T)new Object(); // 编译器无法验证此转换的安全性 } } ``` 上述代码展示了即使指定了 `Box<String>` 或者其他具体的类型实参,到了运行时刻这些信息都被抹去了,只剩下最基础的形式——`Object`。这也就解释了为什么有时候不得不使用强制转(`cast`)并且可能引发潜在的风险如 `ClassCastException`[^4]。 #### 桥接方法的作用 为了让子类能够正确覆盖来自超类的方法定义,特别是在涉及协变返回类型的时候,编译器可能会自动生成所谓的“桥接方法”。例如下面这个简单的继承结构中就包含了这样一个场景: ```java abstract class Container<E> { abstract E getElement(); } class StringContainer extends Container<String>{ @Override String getElement() {return null;} // 编译器自动创建了一个桥接方法以确保向下兼容: // public Object getElement(){return ((String)this.getElement());} } ``` 这里虽然表面上看起来只是简单地覆写了基类里的抽象函数,但实际上为了维持与旧版字节码的一致性互操作性,额外添加了一层间接调用逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值