Java泛型的擦除

在泛型代码内部,无法获得任何有关泛型参数类型的信息。因此,我们可以知道类型参数标识符(如T)和泛型类型边界的信息,却不能知道某个特定实例的实际的类型参数。Java的泛型是使用擦除来实现的,即List<String>和List<Integer>都被擦除为List。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
public class ErasedType {
    public static void main(String[] args) {
        //比较ArrayList<String>和ArrayList<Integer>的类型
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);
        //输出map的参数信息
        HashMap<Integer, String> map = new HashMap<Integer, String>();
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
    }
}
/*输出为
true
[K, V]*/

在擦除的实现中,泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型都被擦除,替换为它们的非泛型上界。如List<T>被擦除为List,未指定上界的普通类型变量被擦除为Object。

可以看到,擦除减少了泛型的泛化性。然而Java仍然使用擦除的主要原因就是泛型不是一开始就支持的特性,而JavaSE 5时才支持的泛型必须可以向上兼容,要使得泛化的客户端可以用非泛化的类库来使用,或者反之亦然。

C++模板与Java泛型的对比

Java泛型的擦除与C++对模板的实现是大不相同的,在C++中我们看如下代码:

#include <iostream>
using namespace std;
template <class T> class Manipulator {
	T obj;
public:
	Manipulator(T x) {
		obj = x;
	}
	void manipulate() {
		obj.f();
	}
};
class HasF {
public:
	void f() {
		cout << "aaa" << endl;
	}
};
int main() {
	HasF hf;
	Manipulator<HasF> m(hf);
	m.manipulate(); //可以正常输出aaa
	return 0;
}

manipulate()方法在obj上调用f(),它怎么知道T类型的obj有f()方法呢?原因是当实例化模板时,C++编译器会进行检查。因此在Manipulator被实例化时,它看到HasF有一个方法f()。如果没有则会得到一个编译报错,因此总是安全的。

再把上面的代码写成Java则编译不会通过。

class HasF {
    public void f() {
        System.out.println("aaa");
    }
}
class Manipulator<T> {
    private T obj;
    public Manipulator(T x) {
        obj = x;
    }
    public void manipulate() {
        obj.f();	//这句报错
    }
}
public class Test {
    public static void main(String[] args) {
        HasF hf = new HasF();
        Manipulator<HasF> m = new Manipulator<HasF>(hf);
        m.manipulate();
    }
}

由于有了擦除,Java编译器无法将T类型的obj要调用f() 对应到 HasF有用f() 这一事实。为了调用f(),我们必须协助泛型类,给定泛型类的边界。即将Manipulator的声明写为class Manipulator<T extends HasF>。边界声明:T必须是HasF或其子类,此时obj就一定有f()可用了。

关于泛型数组

不能用一般的语法创建泛型数组

T[] array = new T[SIZE];	//错误
T[] array = (T)new Object[SIZE];	//可以通过,但是会出现unchecked warning

用这个注解@SuppressWarnings("unchecked")关闭对泛型数组转型的警告

由于擦除,数组的运行时类型就只能是Object[]。如果我们立即将其转型为T[],那么在编译器该数组的实际类型就会丢失,而编译器就可能会错过某些错误检查。因此最好是使用Object[],然后当要使用数组时,添加一个向T[]的转型,即:

class GenericArray<T> {
	private Object[] array;
	//其他代码···
	//需要使用时
	array = (T[])array;
}

泛型不能用于显式地引用运行时类型的操作 之中,例如转型、instanceof、new表达式,因为所有关于参数的类型信息都丢失了。

总之,无论如何,在编写泛型代码时,必须时刻提醒自己,只是看起来拥有有关参数的类型信息而已。比如Manipulator<HasF> m = new Manipulator<HasF>(hf);这句,尽管语法好像表示所有的T都被替换为HasF,但我们必须时刻提醒自己:它只是一个Object罢了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值