美团面试:什么是泛型擦除?泛型擦除原理机制?

文摘   2024-09-20 12:59   四川  

关注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{
@Override public void with(Integer integer) {}}
public class Dog extends Animal implements Run{}//报错

子类与父类继承用一个泛型接口,如果两个泛型类型不同,将会被父类类型劫持。

以上


最后送大家一个福利:

送我原创超30万字阿里架构师进阶专题合集


以及给大家整理最全大厂Java面试题及答案详解,包含:Java、多线程、JVM、Spring、MySQL、Redis、中间件...等必考题答案详解。


需要以上架构专题&面试答案的同学,加我微信即可领取!


添加时备注:资料






mikechen的架构笔记
十余年BAT架构经验倾囊相授!
 最新文章