解析java泛型的的类型擦除
一、Java 泛型的本质
Java 虚拟机中并没有泛型类型对象,所有的对象都是一样的,都属于普通的类。由于 JVM 根本不支持泛型类型,是编译器‚耍了个花招‛,使得似乎存在对泛型类型的支持,它们用泛型类型信息检查所有的代码,但随即‚擦除‛所有的泛型类型并生成只包含普通类型的类文件。C#泛型实现的是类型膨胀,即真实泛型,C#里面泛型无论在程序源码中、编译后的 IL 中或是运行期的 CLR 中都是切实存在的,List<Integer>与 List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据。而 Java 语言中的泛型则不一样,实现的是类型擦除,它只在程序源码中存在,在编译后的字节码文件中,就已经被替
换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的 Java 语言来说,ArrayList<Integer>与 ArrayList<String>就是同一个类ArrayList,Integer 和 String 被擦除了。如:List<Integer> integer = new ArrayList<Integer>();
List<String> string = new ArrayList<String>(); JVM 会去解析泛型语法实现擦除,擦除后语句变成了原始类型:
List integer = new ArrayList();
List string = new ArrayList();
ArrayList<Integer>和 ArrayList<String>类型的实例是相
同的类,如: integer.getClass() = =string.getClass() 编译器只为 ArrayList 生成一个类。所以说 Java 泛型技术实际上是 Java 语言的一个迷惑,JVM 对泛型的编译没有真正使用泛
型的标准原理来实现。
二、类型擦除原则
正确理解泛型概念的首要前提是理解类型擦除,擦除不是一个语言特性,它是 Java 的泛型实现中的一种折中,因为泛型不是Java 语言出现时就有的组成部分,所以这种折中是必需的,目的是为了支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义。正因为泛型实现了类型擦除,因此泛型类型不能在某些重要的上下文环境中使用,而只能在静态类型检查期间才出现,并在此之后,程序中的所有泛型类型都将被擦除,
替换为相应的非泛型。泛型擦除的基本过程体现为以下几个原则:
(1)所有参数化容器类都被擦除成非参数化的(raw type);
如 List<E>、List<List<E>>都被擦除成 List;
(2)所有参数化数组都被擦除成非参数化的数组;如
List<E>[],被擦除成 List[];
(3)Raw type 的容器类,被擦除成其自身,如 List<E>被擦
除成 List;
(4)原生类型(int,String 还有 wrapper 类)都擦除成他
们的自身;
(5)参数类型 E,如果没有上限,则被擦除成 Object;
(6)所有约束参数如<? Extends E>、<X extends E>都被擦
除成 E;
(7)如果有多个约束,擦除成第一个,如<T extends Object
& E>,则擦除成 Object;
由于擦除的实现,减少了泛型的泛化性,所有的强制转换都
为自动和隐式的,提高了代码的重用率。但擦除机制也导致了一
些有用的类型信息丢失,因此在使用时,泛型存在一些约束和限
制。
三、擦除产生的问题
擦除是一种很巧妙的办法,但它有时候会带来一些意想不到
的错误:两个看上去并不相同的泛型类或是泛型方法,由于擦除
的作用,最后会得到相同的类和方法。这种错误,也被称为冲突。
冲突主要发生在下述三种情况。
(一)静态成员共享问题
List<Integer> in= Arrays.asList(1,2,3);
List<String> str = Arrays.asList("one","two");
assert in.getClass() == str.getClass();
in 和 str 两个对象最终被擦除成具有相同类型的(List)的对象,于是这两个对象共享 List 的静态成员,于是就可以得出这样的结论,所有泛化类型的静态成员被其所有的实例化对象共享,因此也就要求所有静态成员不能够是泛化的。 class Coo<T> {
static T value1; //错误
private T value2; //正确
private static List<T> value3 = new ArrayList<T>(); //错误
public T getValue()
{ return value; }
public static List<Object> value4 = new ArrayList<Object>()
//正确
}
出现错误的两个变量 value1 和 value3 都采用不同的形式使用了类型参数 T。由于它们是静态成员,是独立于任何对象的,也可以在对象创建之前就被使用。此时,编译器无法知道用哪一个具体的类型来替代 T,所以编译器不允许这样使用。 (二)重载冲突问题
在一个类的范围内,如果两个函数具有相同的函数名称,不同的参数(返回值不考虑)就互相称为重载函数。擦除带来的另外一个问题是重载的冲突,下面是两个方法的重载:
void conflict(T o){}
void conflict(Object o){}
由于在编译时,T 会被 Object 所取代,所以这两个实际上声
明的是同一个方法,重载就出错了。
(三)接口实现问题
由于接口也可以是泛型接口,而一个类又可以实现多个泛型接口,所以也可能会引发冲突。
class Coo
implements
Comparable<Integer>,
Comparable<Long>
由于 Comparable<Integer>、Comparable<Long>都被擦除成
Comparable,所以这实际上是实现的同一个接口。如:
class Coo<T> implements Comparable<T>
如果要实现多个泛型接口,只能实现具有不同擦除效果的接口。
四、类型擦除的限制
因为 Java 并没有真正实现泛型,而采用擦除机制,造成了Java 泛型本身有很多漏洞。为了规避这些问题,Java 在泛型的使用上除了对上述三种冲突进行约束外,还做了其它的限制。下面是其中一些限制:
(一)不能用泛型类型参数创建实例对象,如:E object=new
E();。因为由于类型擦除,编译时所有泛型类型都会相应的变
为它的原始类型,这时泛型 E 是不可用的。
(二)不能在数据类型转换或 instanceof 操作中使用类型参
数。
(三)不能用基本类型替换类型参数,如:List<int>。基本
类型不属于对象,类型擦除后不能被 Object 或者限定类型替换,
但如果要将基本类型应用到泛型当中,可以用基本类型的包装类。
(四)不能将泛型用在异常捕获和用泛型来实现 Throwable
子类,如:try{}catch (MyException<ex>){}。因为泛型不能扩
展 java.lang.Throwable,这样做将出现编译错误。
(五)不能使用泛型类创建数组。如:ArrayList<String>[]
list=new ArrayList<String>[10];。因为如果先创建一个类型数
组而后转型为 Object 或是先声明类型数组而后创建一个 Object
数组通过强制类型转换来实现类型数组都将导致失败或者是类型
擦除方面的问题。
总结:
泛型是 Java 语言走向类型安全的一大步,它提升了 Java 代
码的健壮性和易用性,但 SUN 本身过分强调向前的兼容性,引入
了擦除,由此产生了不少问题和麻烦。本文重点让大家认识类型
擦除的本质,了解擦除产生的问题及解决方法,旨在帮助大家更
深入的学习泛型。
分享到:
相关推荐
解析Java泛型的类型擦除 Java 泛型是 Java SE 1.5 的新特性,它们在语法和应用环境上与 C++ 中的模板相似,但是本质上它们之间有着区别,这种区别就在于 Java 泛型的类型擦除。 Java 泛型的类型擦除是 Java 语言...
然而,Java泛型的实现机制——类型擦除,也带来了一系列的问题和限制。本文将深入探讨Java泛型擦除的工作原理、它对编程的影响,以及在实际开发中的应对策略。 Java泛型的类型擦除机制是Java泛型实现的核心,它使得...
Java泛型是Java 5中引入的一项强大特性,它...了解泛型的原理、声明方式、类型擦除、边界以及通配符的使用,对于掌握Java编程中的高级技术至关重要。通过合理地使用泛型,我们可以构建出更加健壮和可扩展的Java应用程序
### Java泛型指南经典知识点解析 #### 一、引言 Java 1.5 版本引入了一系列重要的语言特性,其中“泛型”是其中一项关键特性。泛型的引入极大地提高了 Java 语言的类型安全性和代码重用性。本文档旨在深入探讨 ...
由于Java泛型的擦除特性,泛型类型不能用于数组的创建。例如,`new List[3]`是非法的。这是因为数组在Java中是协变的,而泛型为了保持类型安全,不能表现出相同的协变行为。若允许创建泛型数组,可能导致类型安全...
本文旨在深入解析Java泛型的各个方面,帮助开发者形成全面、清晰的理解。 1. **泛型基础** - **泛型类**:泛型类是在类定义时引入类型参数的,如`Box<T>`中的`T`,它代表一个未指定的具体类型。这种设计使得类能够...
可以看到,在编译后,泛型信息被擦除,所有的泛型类型都变成了原始类型 `ArrayList`。 泛型擦除的优点 泛型擦除有两个主要优点: 1. 提高了代码的可读性和可维护性,因为泛型信息在编译期被擦除,编译后的代码...
8. 类型擦除(Type Erasure):Java的泛型在实现时使用了类型擦除技术,这意味着泛型类型信息在编译后不会存在于JVM字节码中。这个机制有其优点也有缺点,优点是兼容旧版Java代码,缺点是无法实现泛型数组等特性。 ...
在这里,`TypeToken`帮助我们传递了泛型类型信息,因为Java编译器通常会擦除泛型信息。 2. **将泛型转为JSON字符串** 要将泛型对象序列化为JSON字符串,你可以直接调用`Gson`的`toJson()`方法。例如,如果你有一...
Java泛型是一个强大的特性,它提供了类型安全的集合操作和代码重用性。通过理解泛型的工作原理和高级特性,开发者可以编写出更安全、更灵活的代码。尽管泛型有一些限制,但它仍然是现代Java编程中不可或缺的一部分。
- **类型擦除的原理**:为了确保与旧版Java代码的兼容性,Java虚拟机(JVM)在运行时并不识别泛型信息,而是通过类型擦除将泛型类型转换为原始类型。原始类型指的是未指定类型参数的具体类型。 - **泛型类的翻译**:...
然而,由于Java的类型擦除,这些类型信息在运行时并不保留,所有泛型类型最终都会被转换为`Object`。 **为什么要使用泛型** 1. **类型安全**:泛型可以防止不兼容类型的对象被添加到集合中,避免了运行时的...
但类型擦除可能导致类型信息的丢失,这在某些情况下可能会引发问题。例如,当一个泛型父类的方法返回一个泛型类型的对象,子类如果覆盖这个方法并返回一个不同类型的对象,虽然编译可以通过,但在运行时可能会导致...
虽然在Java的早期版本中,泛型主要是通过类型擦除实现的,也就是说在运行时泛型信息并不实际存在,但是编译器会插入必要的类型转换,使得未来的JVM优化有了可能性。例如,编译器可以利用更多的类型信息来优化内存...
Class literals 是一种运行时类型标记,它们可以用来获取一个类的信息,这对于泛型类型尤其有用,因为在类型擦除后,编译时类型信息不再可用。 #### 9. More fun with * 在 Java 中,星号通配符(*)有多种用途,...
这意味着泛型信息不会出现在运行时,因此在运行时无法获取泛型类型的信息。 ##### 6.3. 在老代码中使用泛型代码 在使用泛型类时,如果类的对象是由非泛型代码创建的,则需要使用通配符。 ```java List list = new ...
Java泛型相关问题解析 Java中的泛型是一种参数化类型的机制,可以使得代码适用于各种类型,从而编写更加通用的代码。然而,在使用泛型时需要注意一些问题,本文将详细讨论四个泛型相关的问题。 一、泛型类型引用...