上一篇博文中,我们主要介绍了泛型的一些基本使用方法;现在我们来看看泛型的一些较深的特性。
我们也许会声明这样的一组重载方法,来处理String和Integer类型的集合:
class GenericTest {
public void setList(List list){
System.out.println("setList(List list) be called...");
}
public void setList(List list){
System.out.println("setList(List list) be called...");
}
}
按照我们的想法,这里我们实现了一组重载方法,分别对List
Erasure of method setList(List) is the same as another method in type GenericTest
什么意思呢?我们知道一个类中肯定不允许出现两个声明一模一样的方法。而这条编译错误指出:setList(List
我们也许会感到奇怪,编译器为什么会任务setList()的两种重载形式是一致的呢?我们不是明确了不同的类型变量吗?要明白这一点,我们就需要弄清楚泛型中的“类型擦除”这一概念。
类型擦除,顾名思义,我们明确的某个类型变量,如上例中的String,在编译阶段被编译器忽略了,我们看一个示例:
List list = new ArrayList();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
Class.getTypeParameters()会返回一个TypeVariable对象数组,表示有泛型声明所声明的参数;从该方法描述来看,我们貌似可以得到list对象的参数类型信息(这里期望的是String),但是很遗憾,结果返回的是:
[E]
这样得到的还是一个类型标识符,并不是所期望的具体类型:String。在泛型代码中,我们无法获得任何有关泛型参数类型的信息。从这里我们大概可以猜出,List
在具体讲述类型擦除之前,先介绍一个概念:类型变量的限定,因为泛型类型擦除后的原始类型跟类型变量的限定形式有关。
使用泛型时,我们也许希望对某一类的类型能进行比较操作;那么我们在实例化这种类型时,必须要保证该我们明确的这种类型一定是实现了Comparable
class Employee & Serializable> {
T employeeA;
T employeeB;
public Employee(T employeeA, T employeeB) {
super();
this.employeeA = employeeA;
this.employeeB = employeeB;
}
public T getEmployeeA() {
return employeeA;
}
public void setEmployeeA(T employeeA) {
this.employeeA = employeeA;
}
public T getEmployeeB() {
return employeeB;
}
public void setEmployeeB(T employeeB) {
this.employeeB = employeeB;
}
}
/**
* Generic class without limited type variable
*
* @author xzm
* @param
* Generic type
*/
class Pair {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T f, T s) {
first = f;
second = s;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
类Employee
T extends Comparable
意为"T并不是一个任意类型了,它必须是实现了Comparable
class Employee &Comparable & Serializable>
介绍完类型变量的限定内容后,我们就可以讨论类型擦除后的原生类型这一问题了。类型擦除后的原生类型由编译器决定,跟类型变量的状态有关。这时只有两种情况:类型变量未被限定、类型变量被限定。
如果类型变量未被限定,如Pair的定义,那经过类型擦除后的Pair的原始类型是:
class Pair {
private Object first;
private Object second;
public Pair() {
first = null;
}
public Pair(Object f, Object s) {
first = f;
second = s;
}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
public Objectget Second() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
对于Employee,原始类型则是:
class Employee {
Comparable employeeA;
Comparable employeeB;
public Employee(Comparable employeeA, Comparable employeeB) {
super();
this.employeeA = employeeA;
this.employeeB = employeeB;
}
public Comparable getEmployeeA() {
return employeeA;
}
public void setEmployeeA(Comparable employeeA) {
this.employeeA = employeeA;
}
public Comparable getEmployeeB() {
return employeeB;
}
public void setEmployeeB(Comparable employeeB) {
this.employeeB = employeeB;
}
}
无论何时定义一个泛型类型,编译器都自动提供了一个相应的原始类型,编译器也只使用原始类型。原始类型用第一个限定的类型变量来替换(来自类型变量限定列表),如果没有给限定就用Object替换。
依据这条规则,我们得出了上面的Employee和Pair的原始类型。一般地,为了提高效率,我们应该将标签接口(没有方法的接口)放在类型变量限定列表的末尾。
至此,我们回头再看:
class GenericTest {
public void setList(List list){
System.out.println("setList(List list) be called...");
}
public void setList(List list){
System.out.println("setList(List list) be called...");
}
}
就很好理解编译错误所提示的内容了。
最后我们要记住:“即使擦除在方法或类内部移除了有关实际类型的信息,编译器仍旧可以确保在方法或类中使用的类型的内部一致性。”
下一篇文章中,我们会根据类型擦除这个特性,来讨论一些泛型表达式和泛型方法调用的细节问题。