问题导引
这个例子里,定义了两个List集合,不过一个是List泛型类型的,只能存储整数;一个 是List泛型类型的,
只能存储字符串。最后我们通过list1对象和list2对象的 getClass()方法获取他们的类的信息,结果发现结
果为true。说明泛型类型String和 Integer都被擦除掉了,只剩下原始类型。
例如如下代码:
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">package com.fanxing;
import java.util.ArrayList;
import java.util.List;
public class FanTest {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = new ArrayList<String>();
System.out.println(list1.getClass() == list2.getClass());
}
}
他的运行结果是true:
这就是类型擦除,下面咱们来详细查看一下:
类型擦除概念
泛型是java1.5版本才引进的概念,在这之前没有泛型,但是,泛型代码能够很好的和之前版本的代码兼容,原因是泛型只存在代码编译阶段,在进入JVM 之前,泛型相关的信息会被擦除掉,我们称之为类型擦除
类型擦除应用场景
需要值得注意的一点是:类型擦除后保留的原始类型
原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
看下面两个例子理解一下:
案例:原始类型被Object替换
代码如下(不能运行,就是看一下这个特性)
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Pair<T> {
//原始类型就是Object
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public static void main(String[] args) {
Pair pair = new Pair();
pair.setValue();//调用setter方法能看到参数类型是 Object:setValue(Object) ctrl+鼠标放到setValue()中能看到setValue(Object)
}
}
因为在Pair中,T是一个无限定的类型变量,所以用Object代替,其结果就是一个普通的类,如同泛型加入Java之前就已经实现了。在程序中可以包含不同类型的Pair, 如Pair擦除类型后他们就成了原始的Pair 类型,原始类型都是Object。
案例:原始类型被限定类型替换
我们可以用extends设置上届
<pre class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Pair<T extends Comparator> {
//原始类型就是Comparator
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public static void main(String[] args) {
Pair pair = new Pair();
pair.setValue();//调用setter方法能看到参数类型是 Comparator:setValue(Comparator)
}
}
这里相应的原始类型都会被自动提供了,原始类型就用第一个边界的类型替换,也就是Comparator
类型擦除的限制
无法利用同一泛型类的实例区分方法签名
例如下面代码,就会报错:
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Erasure {
//虽然泛型实例不同,但是由于类型擦除后是同一字节码(类型擦拭之后,就都是 List了),因此不能 用于区分方法签名
public void test(List<String> list) {
System.out.println("String");
}
public void test(List<Integer> list) {
System.out.println("Integer");
}
}
虽然泛型实例不同,但是由于类型擦除后是同一字节码(类型擦拭之后,就都是 List了),因此不能 用于区分方法签名 +
泛型类的静态变量是共享的
例如下面代码:
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class StaticTest {
public static void main(String[] args) {
System.out.println(GT.var);
GT<Integer> gti = new GT<>();
gti.var = 1;
System.out.println(GT.var);
GT<String> gts = new GT<>();
gts.var = 2;//泛型参数不同,但是共享类的静态成员
System.out.println(GT.var);
}
}
class GT<T> {
public static int var = 0;
public void nothing(T x) {
}
}
结果如下所示,泛型参数不同,但是共享类的静态成员:
类型擦除的特征
-
所有泛型类的类型参数在编译时都会被擦除,虚拟机运行时中没有泛型,只有普通类和普通方法,从这一点上来说,Java中的泛型从某种程度上是一种语法糖
-
Java泛型不支持基本类型 例如: short int double等
-
在泛型代码内部,无法获得任何有关泛型参数类型的信息,如果传入的类型参数为T,那么在泛型代码内部你不知道T有什么方法,属性,关于T的一切信息都丢失了
-
创建泛型对象时请指明类型,让编译器尽早的做参数检查
-
不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你
-
Java的泛型类型不能用于new构建对象(也不能用于初始化数组).泛型不能用于显性地引用运行时类型的操作之中,例如转型,instanceof和new操作(包括new一个对象,new一个数组),因为所有关于参数的类型信息都在运行时丢失了,所以任何在运行时需要获取类型信息的操作都无法进行工作。