才学了泛型,简单说一下我对java泛型擦除和补偿的一些理解

本文深入探讨Java泛型的类型擦除特性,通过示例解释了在运行时泛型信息如何被擦除,以及如何利用反射进行泛型操作。同时,文章还讨论了泛型补偿现象,即在运行阶段由于类型擦除导致的强制类型转换问题,并展示了如何创建和操作泛型数组。最后,通过一个实际示例展示了如何改进泛型方法,使其能正确返回指定类型的数组。
摘要由CSDN通过智能技术生成

其实Java 的泛型是伪泛型,在JVM运行的时候,所有的泛型信息都会被擦除掉。举个例子,ArrayList<Integer> list,在编译的时候你调用add()方法,只能传入Integer类型的变量,传入其他类型编译器无法通过

ArrayList<Integer> list = new ArrayList<>();
        list.add(3);// 实际上是 list.add(Integer.valueOf(3)); 自动装箱
        //list.add("3");//报错了

  由此可见,对于这个list,在编译阶段我们只能传入Integer类型的变量,这里底层自动装箱,int装化为Integer包装类型。而在运行阶段类型被擦除,字节码中,没有ArrayList<Integer> list,只有ArrayList list,所有元素类型为Object。

这是我们编译的

class Test<T> {
    T value;

    public Test(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在字节码阶段如下:

class Test {
    Object value;

    public Test(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

泛型信息被去掉,全部为Object类型。这就是泛型擦除。

那是不是我们只能传入Integer类型?既然我们编译阶段无法传入其他类型,那么我们利用泛型擦除这一性质,在运行阶段插入,这就用到我们的反射技术。

下面通过反射演示向ArrayList<Integer>类型中插入字符串,并打印出来

ArrayList<Integer> list = new ArrayList<>();
        list.add(2);
        list.add(3);
        Class<? extends ArrayList> aClass = list.getClass();
        Method add = aClass.getDeclaredMethod("add",Object.class);//得到list的add方法,添加元素类型为Object
        add.setAccessible(true);
        add.invoke(list,"我添加了一个字符串");//动态添加元素
        //打印
        System.out.println(list);

得到结果

[2, 3, 我添加了一个字符串]

进程已结束,退出代码为 0

我们也可以单独取出来第三个元素

System.out.println(list.get(2));

//得到如下结果:
我添加了一个字符串

进程已结束,退出代码为 0

但当我们拿出第3个元素,赋值给一个变量的时候,报错了

Integer integer = list.get(2);//编译不报错,运行报错
Object value= list.get(2);//编译不报错,运行也不报错
String ww = String.valueOf(list.get(2));//这样也可以,编译不报错,运行也不报错


//运行结果
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at generics.Demo.main(Demo.java:21)

进程已结束,退出代码为 1

百度可知  ,ClassCastException: 当试图将对象强制转换为不是实例的子类时,抛出该异常;

这是一个转型错误,具体可看Java异常ClassCastException - 街头卖艺的肖邦 - 博客园

 这是个String类型的,但我们拿出来因为泛型补偿,会强制转化为Integer类型,String与Integer没有继承关系,所以报错ClassCastException异常。

泛型补偿说简单点就是,在运行阶段将泛型拿出来,原先因为擦除变为Object类型的元素,拿出来时要强制转化为该泛型类型,上面例子就是因为泛型补偿,拿出来赋值时编译器认定它是Integer,但实际类型为String,所以报错。一句话就是,你可以拿出来直接打印,但不能赋值,或者向下转型赋值用Object类型的变量指向它。

此外,泛型对象指定的类型没有默认构造器,就比如:

E e=new E()

E e=new E[]

但可以声明泛型

E e

E[] e

这些就要报编译错误。那是不是我们就无法创建泛型对象了?

我们可以声明一个Object类型的数组,然后强制转化

E[] e=(E[])new  Object[]

下面我们看一个例子,编写一个方法,传入一个任意类型的数组,去掉最后一个元素,返回新数组。

    public static <T> T[] fun(T[] t) {
        T[] arr = (T[]) new Object[t.length - 1];
        System.arraycopy(t, 0, arr, 0, t.length - 1);
        return arr;
    }

然后在main函数调用函数

String[] arr = {"ds", "323", "4lk"};

        Object[] fun = fun(arr);
//        String[] funa = fun(arr);//运行报错,因为实际类型是Object[],我们无法让String[]类型指向Object[]类型
        System.out.println(Arrays.toString(fun));

我们只能用Object[] 来接收该返回的数组,因为实际类型是Object,如果用String[]来接收,虽然没有错,但运行报错

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
	at generics.MyGenerics.main(MyGenerics.java:37)

显示不能将object转化为String

为了解决该问题,我们也通过反射动态创建泛型数组。

    /**
     * 通过反射来改进
     * 传入String[],返回一个编译类型也为String[]的数组
     * Array.newInstance(元素类型,长度)  返回一个Object类型的对象,实际类型为String[]
     */
    public static <T> T[] fun2(T[] t) {
        Class<? extends Object[]> aClass = t.getClass();
//        System.out.println(aClass.getTypeName());//获取该对象类型:java.lang.String[]
//        System.out.println(aClass.getComponentType());  //获取数组对象的元素类型:class java.lang.String
        T[] res = (T[]) Array.newInstance(aClass.getComponentType(), t.length - 1);//返回一个编译类型为Object类型的数组
//        System.out.println(res.getClass().getTypeName());//这里已经看到为:java.lang.String[]
        System.arraycopy(t, 0, res, 0, t.length - 1);
        return res;
    }

然后我们在main函数用String[]来接收,编译通过,运行也通过

String[] res = fun2(arr);//运行不报错
        System.out.println(Arrays.toString(res));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luncker(摆烂版)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值