《Java核心技术卷一 第10版》读书笔记之Java泛型之类型擦除与局限性
以下内容参考文献包括:《java核心技术卷一 第10版》和 技术博客
类型擦除
要正确理解java的泛型,就需要了解类型擦除。
《java核心技术卷一》:无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用Object)。
白话:java的泛型是伪泛型,在编译期间,所有的泛型信息会被擦掉,java在生成字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
我们可以通过代码来验证这一类型擦除过程
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(10);
// 输出结果:true
System.out.println(list1.getClass() == list2.getClass());
}
由上面的代码可以看出,两个ArrayList所存储的数据类型是不一样的,但是通过getClass()获取类的类型却是一样的,说明String和Integer被擦除了,变成原始类型。
这里有个概念:原始类型
原始类型:擦除了泛型信息后,在字节码中的类型变量的真正类型。
《java核心技术卷一》中对原始类型的例子
我们定义泛型类和泛型方法
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Pair会变成这样子,所有的泛型信息都被自动替换成另一种类型了,这个类型就是原始类型
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
《java核心技术卷一》:如果类型变量有限定,那么原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。例如, 类 Pair<T>
中的类型变量没有显式的限定, 因此, 原始类型用 Object 替换 T。
看下面的例子:
public class Pair<T extends Comparable & Serializable> implements Serializable {
private T value;
public Pair(T value) {
....
}
}
类型擦除后,变成这样子
public class Pair implements Serializable {
private Comparable value;
public Pair(Comparable value) {
....
}
}
如果限定变成这样子
class Pair<T extends Serializable & Comparable>
那么类型擦除的时候是用Serializable替换掉T的
所以,为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾
泛型的局限性
由上面可以知道Java的泛型是伪泛型,编译时使用了类型擦除,所以我们在使用Java泛型时需要考虑一些限制。
不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此, 没有 Pair<int>
, 只 有 Pair<Integer>
。 当然,其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 int值。
这的确令人烦恼。但是,这样做与 Java 语言中基本类型的独立状态相一致。这并不是一个致命的缺陷——只有 8 种基本类型, 当包装器类型(wrapper type) 不能接受替换时, 可以使用独立的类和方法处理它们。
运行时类型查询只适用于原始类型
查询一个对象是否属于某个泛型类型时,倘若使用 instanceof
会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
if (a instanceof Pair<String>) // 编译错误
if (a instanceof Pair<T>) // 编译错误
Pair<String> p = (Pair<String>) a; // 警告
getClass方法总是返回原始类型,例如:
Pair<String> stringPair = . .
Pair<Employee> employeePair = . .
if (stringPair.getClass() == employeePair.getClass()) // 结果:true
两次调用getClass都返回Pair.class
不能创建参数化类型的数组
例如:
Pair<String>[] table = new Pair<String>[10]; // 编译错误
如果需要收集参数化类型对象, 只有一种安全而有效的方法:使用 ArrayList:
ArrayList<Pair<String>>
不能实例化类型变量
不能使用像 new T(...)、 newT[...] 或 T.class 这样的表达式中的类型变量,例如:
public class Pair<T> {
private T first;
private T sencond;
// Pair的构造器
public Pair() {
first = new T(); // 编译错误
second = new T(); // 编译错误
}
}
在 Java SE 8 之后,最好的解决办法是让调用者提供一个构造器表达式。例如:
Pair<String> p = Pair.makePair(String::new);
makePair 方法接收一个 Supplier<T>
,这是一个函数式接口,表示一个无参数而且返回类型为 T 的函数:
public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get0. constr.get0);
}
或者,这么写也可以:
public static <T> Pair<T> makePair(Class<T> cl) {
try {
return new Pair<T>(cl.newInstance(), cl.newInstance());
} catch(Exception e) {
return null;
}
}
Pair<String> p = Pair.makePair(String.class);
泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。例如:
public class Singleton<T> {
private static T singlelnstance; // Error
public static T getSinglelnstance() { // Error
if (singleinstance == null) construct new instance of T
return singlelnstance;
}
}
类型擦除之后, 只剩下 Singleton 类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量的静态域和方法。
不能抛出或捕获泛型类的实例
实际上, 甚至泛型类扩展 Throwable 都是不合法的。例如, 以下定义就不能正常编译:
// Error can't extend Throwable
public class Problem<T> extends Exception {
/* . . . */
}
catch 子句中不能使用类型变量。例如, 以下方法将不能编译:
public static <T extends Throwable> void doWork(Class<T> t) {
try{
do work
} catch (T e) { // Error can 't catch type variable
Logger,global.info(...)
}
}
注意擦除后的冲突
当泛型类型被擦除时, 无法创建引发冲突的条件。下面是一个示例。假定像下面这样将equals 方法添加到 Pair 类中:
public class Pair<T> {
public boolean equals(T value) {
return first,equals(value) && second,equals(value);
}
}
考虑一个 Pair<String>。从概念上讲, 它有两个 equals 方法:
boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object
但实际上,方法擦除了 boolean equals(T)
就是 boolean equals(Object)
,这会与Object.equals方法发送冲突。
网友评论