互联网大厂面试什么是Java语言中的类型擦除?
什么是泛型?
首先来讲泛型操作是从JDK5开始引入Java语言的一个新特性,其允许在定义类或者是接口的时候使用一个类型参数,并且这个类型参数可以在使用的时候被替换成一个具体的参数。使用泛型最大的好处就是可以极大的提升代码的复用性。
以List为例,我们可以在List集合中放入任何的数据类型例如String、Integer等等,当然也可以放入对象类型。如果不使用泛型那么就会出现想要使用String类型的List集合的时候需要定义一个String类型的List集合,需要使用Integer类型的List的集合的时候就需要定义相应类型的集合,这样做就有点复杂了,为了解决这样的问题,就出现了我们的泛型操作。各种语言对泛型操作的处理机制?
通常情况下,编译器需要通过如下的几种方式来对泛型数据类型进行处理。在实例化一个泛型类或者是一个泛型类型的方法的时候,对于每一种数据类型都产生一份新的代码来进行具体的支撑,例如针对List的泛型操作,如果使用了String类型,就生成一份String类型的List代码,使用Integer就生成一份Integer的List代码。对于每个使用泛型操作的类或者方法来讲生成的代码都是唯一的一份,并且将所有的类型通过映射都映射到泛型上,在需要具体使用的时候对类型进行判断进行转换,使得泛型能够正常的操作。
在C++语言中的泛型就是通过第一种方式来实现的,也就是说C++语言的编译器会为每个泛型都生成一份对应的执行代码,在代码中String类型与Integer类型对应的泛型类是不一样的。而这样带来的后果就是在编译后的代码中会存在大量的冗余代码。
在C#语言中泛型操作无论是在源码中还是在编译后的中间代码中或者是在运行时期的代码中都是存在不同类型的数据代码,唯一不同的是这些代码是在运行期间生成的,并且有自己的虚地址表示,这种方式被称为是类型膨胀。而基于这种方式所实现的泛型操作被称为是真实泛型。
在Java语言中的泛型与前面两者是不一样的,它只在源代码中存在,在编译之后的字节码中就会被替换成原生类,并且在相应的代码中加入了强制类型转换。所以对于Java语言而言,ArrayList和ArrayList其实就是同一个类型,对于Java来讲泛型操作可以称之为Java语言的优势,而在Java语言中使用到的对泛型的操作则是被称为是类型擦除,而基于这种方法实现的泛型操作被称为是伪泛型。
C++和C#是使用Code specialization的处理机制,前面提到,他有一个缺点,那就是会导致代码膨胀。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类型实例映射到唯一的字节码表示是通过类型擦除(type erasure)实现的。什么是Java语言的类型擦除?
什么是类型擦除?类型擦除是指通过类型参数组合的方式,将泛型类型的实例对象关联到同一份字节码文件上。编译器最终只会为泛型类型提供一份统一的字节码,并且将相关的实例对象关联到这一份字节码文件上。
类型擦除的关键就是在于从泛型类型中清除类型参数的信息,并且在使用的时候需要对传入的真实参数进行类型检查和类型转换。可以简单的理解为将泛型类型的代码转换成了普通类型的代码,只不过对于Java编译器来讲,是将对应的字节码文件进行了转换。
类型擦除器过程主要分为如下两个步骤将所有的泛型有关的参数用最顶级的父类进行替换移除所有的具体类型相关的参数Java语言泛型处理过程
首先来看一段简单的代码public static void main(String[] args) { Map map = new HashMap(); map.put("name", "nihui"); map.put("age", "22"); System.out.println(map.get("name")); System.out.println(map.get("age")); }
经过反编译之后的结果如下public static void main(String[] args) { Map map = new HashMap(); map.put("name", "nihui"); map.put("age", "22"); System.out.println((String) map.get("name")); System.out.println((String) map.get("age")); }
从反编译的结果来看,我们对于KV键值对的处理都已经不存在了,只留下了我们想要实现的部分的代码,就是直接进行类型的存储。interface Comparable { public int compareTo(A that); } public final class NumericValue implements Comparable { private byte value; public NumericValue(byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo(NumericValue that) { return this.value - that.value; } }
反编译之后的结果 interface Comparable { public int compareTo( Object that); } public final class NumericValue implements Comparable { public NumericValue(byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo(NumericValue that) { return value - that.value; } public volatile int compareTo(Object obj) { return compareTo((NumericValue)obj); } private byte value; }
编译之前的代码public class Collections { public static > A max(Collection xs) { Iterator xi = xs.iterator(); A w = xi.next(); while (xi.hasNext()) { A x = xi.next(); if (w.compareTo(x) < 0) w = x; } return w; } }
反编译之后的代码public class Collections { public Collections() { } public static Comparable max(Collection xs) { Iterator xi = xs.iterator(); Comparable w = (Comparable)xi.next(); while(xi.hasNext()) { Comparable x = (Comparable)xi.next(); if(w.compareTo(x) < 0) w = x; } return w; } }
第2个泛型类Comparable 擦除后 A被替换为最左边界Object。Comparable的类型参数NumericValue被擦除掉,但是这直 接导致NumericValue没有实现接口Comparable的compareTo(Object that)方法,
于是编译器在其中添加了一个桥接方法。 从第3个示例中限定了类型参数的边界>A,A必须为Comparable的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界Comparable,然后去掉参数类型A,得到最终的擦除后结果。泛型带来的问题有哪些?
一、泛型方法的重载public class GenericTypes { public static void method(List list) { System.out.println("invoke method(List list)"); } public static void method(List list) { System.out.println("invoke method(List list)"); } }
从上面这段代码中我们可以看到两个方法由于参数类型不同形成了方法的重载,但是这段代码在实际编译的过程中是没法通过的,因为在之前的内容中我们提到过List和List在Java语言来看由于类型擦除会将其认定为同一个类型,这样就会变成两个参数一样并且方法名一样的方法,这个在Java操作中是不被允许的。
二、遇到Catch
我们都知道在Java语言中会经常遇到异常捕获,如果我们在开发过程中自定义了一个通用的异常处理,那么就会有各种各样的异常被通用的异常处理捕获,这个时候如果想要实现不同的异常进行不同的处理那就非常难了
三、当泛型类型包含静态变量public class StaticTest{ public static void main(String[] args){ GT gti = new GT(); gti.var=1; GT gts = new GT(); gts.var=2; System.out.println(gti.var); } } class GT{ public static int var=0; public void nothing(T x){} }
如上面代码所示,如果在泛型中遇到的静态变量,会出现什么样的结果呢?我们都知道静态变量是与类的存在是有关的,也就是说对于一个泛型如果与到了一个静态变量,那么这个静态变量就是对于所有的泛型类所共享的。而上面的代码执行结果也就显而易见了。输出的结果应该是2。因为对于第一个操作来讲,将泛型中的静态变量改成了1,但是由于静态变量是共享的,在第二个操作中就会将对应的值改成2,所以最终的输出结果应该是2。总结
综上所述,在虚拟机中没有泛型的概念,只有普通类与普通方法的概念,所有泛型类型在编译之后都会被进行类型擦除操作,也就是说泛型并没有自己独立存在的对象进行存储,也就不会有反射等一些高级操作。在使用泛型操作的时候一定要提前的去对类型做处理让编译器提前就知道需要处理的类型的是什么。这样可以在提升编译器处理效率的同时也可以避免带来各种各样的问题。
王者荣耀黄金排位禁的英雄都有哪些?黄金段位排位赛不是征兆模式,因此不能BAN英雄。回答完啦其实小编我一般随到45楼都是队友让我禁啥我就禁啥,大气都不敢出,生怕人家生气挂机(),然后注意下队友常用英雄,尽量别禁这些,
英雄联盟虚空是不是最强的势力?lol里面的势力有很多,比如人间的死对头,德玛西亚和诺克萨斯。还有战争学院班德尔城的小矮子们暗影岛的恶徒弗雷尔卓德的冰天雪地艾欧尼亚以及恕瑞玛等,大大小小总共有26个势力生存在瓦罗
王者荣耀里大家都说代代版本削宫本,到底削了什么?以前的宫本武藏是怎么样的?首先,宫本武藏是王者荣耀比较早期上线的一名英雄,而且宫本武藏最初登场时是一名刺客,与李白一起成就了当时的刺客荣耀,对王者峡谷形成了绝对的统治。故而,宫本武藏在早期是王者荣耀中比较具
你觉得魔兽争霸3的英雄中,哪位英雄是最强的英雄杀手?剑圣进不了前3。魔兽争霸3游戏中英雄杀手排行榜。欢迎补充!top5恶魔猎手dh埃辛诺斯的双刃!单挑情况下不虚任何人,也是法师杀手。敏捷英雄的身材,却有着力量英雄的肉度,可能他杀不死
王者荣耀的玩家们,敢不敢晒一下你的本命英雄?万年单排王来咯,跟大家一起晒本命英雄!先来晒三个,看看有没有也是你的本命英雄呢?首先,关羽闪亮登场从最初的绿帽关羽,到现在终于给他穿上了新春的衣服!都说关羽的操作难度在王者英雄当中
英雄联盟中小鱼人的强势期是多久?一看见对面小鱼人中单,我反手一个德玛,对线舒服的很,清兵推塔就好了!首先说小鱼人属于刺客,一般情况下,刺客分物理和魔法,小鱼主要是魔法,刺客的输出主要来源技能和平a的高爆发,所以只
近年春晚小品歌舞更丰富多彩,为什么反倒感觉不如以前吸引人了?感谢邀请。首先说歌舞,春节中国人的节日,欢声笑语的,热热闹闹的,但总不能标新立异搞一些特殊作品吧?!况且随着网络神曲,流量明星的出现,水平不见得高节目不见得好,明星为了流量为了出名
怎样改掉驼背的坏习惯?说到驼背,与其密切相关的就是头前引和圆肩,三者同属于上交叉综合症,我们今天就一起讲一下。表现先来看一下头前引,其实很多明星都有这种体态问题,请看左边Taylor的姿态。再看一下圆肩
哈尔滨私立高中哪个好一些,收费标准是多少?私立高中目前最好的是德强,其次是工附松雷,学费每年21000加上饭费是30000,加住校加校车大概35000。能去市重点以上的不要考虑私立高中。每个月大小晚课1000,假期补课每天
孕妇一定不能用药吗?怀孕期间是不是绝对不能用药呢?现在社会的理念就是怀孕了不能用药,会导致胎儿的发育不良甚至出现胎儿的畸形。确实在上个世纪60年代,反应停的广泛应用导致很多畸形儿的出生,也有其他药物引
打算换一个能用3,4年的手机,应该换4g的还是5g的?谢谢邀请。现在智能手机基本普及,大多数人使用的仍是4G智能手机。第一,现在智能手机无论在性能上,价格上都已经适合大多数人的需求。一般千元左右的智能手机基本能满足大多数人的使用需求。