关注△mikechen的架构笔记△,十余年BAT架构经验倾囊相授
大家好,我是mikechen。
最近有同学被大厂问到了:泛型擦除?实现机制?,本篇就重点来详解Java泛型擦除?以及实现机制@mikechen
最新mikechen原创超30万字《阿里架构师进阶专题合集》和《最全大厂面试题及答案总结》,请关注本公众号【mikechen的架构笔记】,后台回复:资料,即可领取。
Java泛型擦除
在 Java 中的 泛型 ,常常被称之为 伪泛型 ,究其原因是因为在实际代码的运行中,将实际类型参数的信息擦除掉了 (Type Erasure) 。
Java的泛型只在编译时有效,到了运行时这个泛型类型就会被擦除掉,即List和List在运行时其实都是List类型。
那是什么原因导致了 Java 泛型擦除呢?下面我就带着大家以 Java案例来详解。
Java泛型擦除原理
先看一个简单的例子,看看Java泛型擦除的案例,代码如下:
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}
我们定义了两个ArrayList数组,一个是ArrayList<String>泛型类型的,只能存储字符串,一个是ArrayList<Integer>泛型类型的,只能存储整数。
上面代码最终结果输出的是什么?不了解泛型的和很熟悉泛型的同学应该能够答出来,而对泛型有所了解,但是了解不深入的同学可能会答错。
正确答案是 true,难道不是false?为什么是这样?
这是因为判断
System.out.println(list1.getClass() == list2.getClass());
泛型类型String和Integer都被擦除掉了,只剩下原始类型。
比如ArrayList<Integer>和ArrayList<String>等类型,在编译后都会变成ArrayList,JVM看到的只是ArrayList,泛型附加的类型信息对JVM来说是不可见的。
那怎么擦除为原始类型呢,我们接着聊。
泛型原始类型详解
原始类型顾名思义就是没有泛型的最初类型,即擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。
没有限定类型时,类型变量被擦除,原始类型为Object,看一个案例:
class Colleage<T>{
private T student;
public Colleage(T student){
this.student=student;
}
public T getStudent() {
return student;
}
public void setStudent(T student) {
this.student = student;
}
}
上例中Colleage类在经过编译之后,就成为原始的Colleage类了,因为泛型形参T是一个无限定的类型变量,所以它的原始类型为Object。
有限定类型时,类型变量被擦除,原始类型是其限定类型。
对于有多个限定的类型变量,那么原始类型就用第一个边界的类型变量来替换。
class Colleage <T extends Comparable & Serializable>{
此时Colleage的原始类型就是Comparable
class Colleage <T extends Serializable & Comparable>{
Java泛型擦除问题与解决方案
1.泛型不能用于显式地引用运行时类型的操作之中
例如instanceof、new
class Erased{
public void f(T t,String a){
//T t = new T(); //error
//T[] ts = new T[100]; //error
//boolean k = a instanceof T; //error
}
}
我们可以把具体类型的Class传进来解决部分问题
class Erased{
Class kind;
public Erased(Class kind){
this.kind = kind;
}
public void f(String a) throws Exception{
T t = kind.newInstance(); //这个类需要有默认构造器,反射知识
T[] ts = (T[]) Array.newInstance(kind,10);
boolean k = kind.isInstance(a);
}
}
2.基本类型不能作为参数类型
由于擦除的原因,类型参数会被擦除到上一边界,边界是一个类,不兼容基本类型,所以不能声明List。
Java1.5后有自动包装机制,可以用List实现基本类型的泛化,但是要注意int[]与Integer[]是转化的,在一些需要传Integer[]的地方不能传int[].
3.数组
可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
List[] stringLists; //可以声明带泛型的数组引用
List[] stringLists2 = new ArrayList[1];//不能直接创建带泛型的数组对象
List[] stringLists3 = new ArrayList[1];
可以通过Array.newInstance(Class,int)来创建T[]
4.重载
class Holder<t,e>{
void f(List list){}
void f(List list){}
}
</t,e>
上面的代码将会编译不通过,由于擦除的原因,这两个方法的类型签名是一样的。
5.基类劫持了接口
interface Run{
void with(T t);
}
class Animal implements Run{
public void with(Integer integer) {}
}
public class Dog extends Animal implements Run{}//报错
子类与父类继承用一个泛型接口,如果两个泛型类型不同,将会被父类类型劫持。
以上
最后送大家一个福利:
送我原创超30万字阿里架构师进阶专题合集。
以及给大家整理最全大厂Java面试题及答案详解,包含:Java、多线程、JVM、Spring、MySQL、Redis、中间件...等必考题答案详解。
需要以上架构专题&面试答案的同学,加我微信即可领取!
添加时备注:资料