其实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));