一、概述
Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变),奇怪的ClassCastException等。 正确的使用Java泛型需要深入的了解Java的一些概念,如协变,桥接方法,以及这篇笔记记录的类型擦除。Java泛型的处理几乎都在编译器中进行,编译器生成的bytecode是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。
二、编译器如何处理泛型?
通常情况下,一个编译器处理泛型有两种方式:
1.Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
2.Code sharing。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
C++中的模板(template)是典型的Code specialization实现。C++编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer list和string list是两种不同的类型。这样会导致代码膨胀(code bloat),不过有经验的C++程序员可以有技巧的避免代码膨胀。
Code specialization另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
三、 什么是类型擦除?
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。
类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
类型擦除的主要过程如下:
1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2.移除所有的类型参数。
如
interface Comparable <A> {
public int compareTo( A that);
}
final class NumericValue implements Comparable <NumericValue> {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
}
-----------------
class Collections {
public static <A extends Comparable<A>>A max(Collection <A> xs) {
Iterator <A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList <NumericValue> numberList = new LinkedList <NumericValue> ();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
经过类型擦除后的类型为
interface Comparable {
public int compareTo( Object that);
}
final class NumericValue implements Comparable {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
public int compareTo(Object that) { return this.compareTo((NumericValue)that); }
}
-------------
class 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;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList .add(new NumericValue((byte)0)); ,
numberList .add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
第一个泛型类Comparable <A>擦除后 A被替换为最左边界Object。Comparable<NumericValue>的类型参数NumericValue被擦除掉,但是这直 接导致NumericValue没有实现接口Comparable的compareTo(Object that)方法,于是编译器充当好人,添加了一个桥接方法。
第二个示例中限定了类型参数的边界<A extends Comparable<A>>A,A必须为Comparable<A>的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界Comparable<A>,然后去掉参数类型A,得到最终的擦除后结果。
四、类型擦除带来的问题
正是由于类型擦除的隐蔽存在,直接导致了众多的泛型灵异问题。
Q1.用同一泛型类的实例区分方法签名?——NO!
import java.util.*;
public class Erasure{
public void test(List<String> ls){
System.out.println("Sting");
}
public void test(List<Integer> li){
System.out.println("Integer");
}
}
编译该类,
参数类型明明不一样啊,一个List<String>,一个是List<Integer>,但是,偷偷的说,type erasure之后,它就都是List了⋯⋯
Q2. 同时catch同一个泛型异常类的多个实例?——NO!
同理,如果定义了一个泛型一场类GenericException<T>,千万别同时catch GenericException<Integer>和GenericException<String>,因为他们是一样一样滴⋯⋯
Q3.泛型类的静态变量是共享的?——Yes!
猜猜这段代码的输出是什么?
import java.util.*;
public class StaticTest{
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
五、Just remember
1.虚拟机中没有泛型,只有普通类和普通方法
2.所有泛型类的类型参数在编译时都会被擦除
3.创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
4.不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
相关推荐
解析Java泛型的类型擦除.pdf
Java中的泛型,在运行时刻其具体类型是被擦除的,这样我们就不能用new T(),instanceof等关操作,特别是对泛型类型的类的实例化问题,在此根据《Thinking in Java》中所讲的对类型擦除所带来问题的三种解决方案,比较...
- 泛型擦除前的例子把这段Java代码编译成Class文件,然后再用字节码反编译后,將会发现泛型都不见了,又变回了Java泛型出现之前的写法,泛型类型都变回了原
NULL 博文链接:https://hyzhaolt.iteye.com/blog/443661
这个过程由“类型擦除”实现。但是并非像许多开发者认为的那样,在 <..> 符号内的东西都被擦除了。看下面这段代码: public class ClassTest { public static void main(String[] args) throws Exception { ...
本文主要介绍了Java泛型的使用以及类型擦除相关的问题。具有很好的参考价值。下面跟着小编一起来看下吧
JAVA泛型 28 类型擦除 28 实例分析 29 通配符与上下界 30 类型系统 31 开发自己的泛型类 32 最佳实践 32 参考资料 33 目录 3 JAVA注解 34 使用注解 34 开发注解 35 处理注解 35 实例分析 38 参考资料 39 JAVA反射与...
详细的介绍了Java是伪泛型的原因,介绍了类型擦除的内容等。
Java 泛型最全指南(定义和使用+继承泛型类/实现泛型接口+泛型的边界+通配符+类型擦除)
2. 类型擦除:Java泛型在编译时实现类型安全检查,但在运行时会将泛型类型的信息擦除,转换为原始类型。这是为了保持与Java早期版本的兼容性,同时减少运行时的开销。例如,List在运行时会被擦除为List。 3. 上限和...
主要介绍了详解java 中泛型中的类型擦除和桥方法的相关资料,需要的朋友可以参考下
环境:Windows XP Professional、JDK 1.6、Ant 1.7 说明:Java泛型的动机是为解决类型转换在编译时不报错的问题。另外由于“范型编程”(Generic Programming)的推广,于是2004年JDK 5.0引用范型标准。本例子说明...
学习和理解Java泛型的基本概念和语法; 实际项目中需要使用泛型来增加类型安全性和重用性的开发任务。 目标: 本代码资源的目标是帮助读者理解泛型的用法和优势,并通过实际的示例代码加深对泛型的掌握。读者可以...
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛...泛型是参数化类型的应用,操作的数据类型不限定于特定类型,可以根据实际需要设置不同的数据类型,以实现代码复用。下面小编来简单讲一讲泛型
泛型与集合 使用 进行初步翻译. 将利用碎片时间进行整理和校对,完整的时间段适合做其他需要大量思考的事,如果你有兴趣欢迎提交PR。 TODO 数据校对 目录 2.4 获取和放置原则 2.5 数组 2.6 通配符与类型参数 2.7 ...
主要介绍了Java编程探索之泛型擦除实例解析,具有一定参考价值,需要的朋友可以了解下。
本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除。 泛型基础 泛型类 我们首先定义一个简单的Box类: public class Box { private String object; ...
1、Java泛型的实现机制是怎样的? Java 泛型采用 类型擦除实现 类型编译时被擦除为 object, 不兼容基本类型 类型擦除的实现方案主要考虑向后兼容 泛型类型签名信息特定场景下反射可获取 结合项目实践, 例如 Gson...
Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用。这篇文章主要介绍了Java类型擦除机制,需要的朋友可以参考下
什么是类型擦除?介绍一下常用的通配符? 常用的通配符有哪些? 你的项目中哪里用到了泛型? 反射 何为反射? 反射机制优缺点 反射的应用场景 注解 异常 Exception 和 Error 有什么区别? Checked Exception 和 ...