泛型程序设计(Generic parogramming)意味着编写的代码可以被很多不同类型的对象所重用。
泛型,提供了类型参数(type parameters),使得程序具有更好的可读性和安全性。
例如:
1
2 1 ArrayList<String> files = new ArrayList<String>();
2
1
2 1 表明了ArrayList类有一个类型参数String来指示元素的类型。
2
泛型类
泛型类的简单实例:
1
2 1 Pair class Pair<T, U>{......}
2
泛型类的定义:一个泛型类就是具有一个或多个类型变量的类。如上述泛型类Pair具有两个类型变量(T和U)。
类型变量:用大写的T(或U,S)表示普通类型变量,E表示集合的元素类型,K和V分别表示表的关键字与值的类型。
实例化泛型类型:用具体的类型替换类型变量就可以实例化泛型类型。
泛型方法
泛型方法可以定义在普通类中,也可以定义在泛型类中。
例如:
1
2
3
4 1 public static <T> T getMiddle(T[] a){
2 return a[a.length / 2];
3 }
4
1
2 1类型变量<T>,要放在修饰符的后面,返回类型的前面。
2
调用泛型方法,在方法名前的尖括号中放入具体的类型。
1
2
3
4 1 String[] names = {"john", "Bob", "Lily"};
2 String middle = ArrayAlg.<String>getMiddle(names);
3
4
1
2 1 但是,大多数情况在,我们可以省略类型参数,直接调用方法。
2
1
2
3 1 String[] names = {"john", "Bob", "Lily"};
2 String middle = ArrayAlg.getMiddle(names);
3
类型变量的限定
有时候,我们必须要求某个类型变量必须实现某个接口再能让程序正确执行,比如 public static <T> T min(T[] a){ … }方法,很容易知道T必须是实现了Comparale接口才能得到最小值。我们只需要:
1
2 1 public static <T extends Comparable> T min(T[] a){ ... }
2
1
2 1这表示,min方法只能被实现了Comparable接口的类的数组调用。
2
<T extends BoundingType>,T是绑定类型(BoundingType)的子类型,BoundingType可以是类或者接口。当BoundingType有多个时,用&分隔,且限定中至多有一个类,如果有类,则类必须是限定列表中的第一个。
泛型代码和虚拟机
虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义一个泛型类型,都自动提供一个相应的原始类型(raw type)。
原始类型用第一个限定的类型变量来替换,如果没有限定就用Object来替换。
翻译泛型表达式:
当调用泛型方法是,如果擦除返回类型,
编译器会插入强制类型转换。
例如:
1
2
3 1 Pair<Employee> buddies = ......
2 Employee buddy = buddies.getFirst();
3
1
2 1getFirst方法在泛型中返回一个Employee类型的对象,但是类型擦除后,返回一个Object类型的对象,这时,编译器会自动插入(Employee)buddies.getFirst(),强制类型转换。
2
翻译泛型方法:
public static <T extends Comparable> T min(T[] a) —> public static Comparable min(Comparable[] a)
合成的桥方法:
这是为了解决类型擦除导致的多态性丧失而创造的方法。
小例子:
擦除类型前:
泛型类Pair:
1
2
3
4
5 1 public class Pair<T>{
2 ........
3 public void setSecond(T second){......}
4 }
5
一个普通的类:
1
2
3
4 1 class DateInterval extends Pair<Date>{
2 public void setSecond(Date second){...}
3 }
4
擦除类型后
Pair类变成了:
1
2
3
4
5 1 public class Pair{
2 ........
3 public void setSecond(Object second){......}
4 }
5
这个DateInterval类变成了:
1
2
3
4 1class DateInterval extends Pair{
2 public void setSecond(Date second){...};
3}
4
可以看出,两个setSecond方法的参数完全不同,他们是两个完全不同的方法,如果运行如下语句:
1
2
3
4 1 DateInterval interval = new DateInterval(...);
2 Pair<Date> pair = interval;
3 pair.setSecond(aDate);
4
1
2 1那么,最后一句,pair.setSecond(aDate)是调用的那个方法呢?下面来分析一下:
2
setSecond(Date second)方法仅在DateInterval中有效,setSecond(Objiec second)在Pair及其子类中都有效。这里已经擦除了类型,所以pair.setSecond(aDate)中的pair为Pair类的对象,必将调用Pair类中的那个setSecond(Object second)方法。这样,setSecond本应具有的多态性消失了。
注意:DateInterval类并没有重写setSecond方法。
所以,要利用合成的桥方法重新找回多态性。
合成的桥方法只需要在DateInterval类中加一个setSecond(Object second)方法:
1
2
3
4
5
6
7 1class DateInterval extends Pair{
2 public void setSecond(Date second){...};
3 public void setSecond(Object second){
4 setSecond((Date)second);
5 }
6 }
7
就这样,setSecond(Object second)重写了超类中的同签名方法,实现了多态性。
约束与局限性
- 约束与局限性大多是由类型擦除引起的。
- 不能用基本类型实例化类型参数。由于primitive type不是Object的子类。
- 运行时类型查询只适用于原始类型(raw type)。a instanceof Pair<T>与a instanceof Pair<String>有相同结果,T被忽略了。
- 不能抛出也不能捕获泛型类实例。
- 参数化类型的数组不合法。Pair<String>[] myTest = new Pair<String>[10]; //错误
- 不能实例化类型变量。new T(…);//错误
- 泛型类的静态上下文中类型变量无效。
- 注意擦除后的冲突。
通配符(wildcard type)
Pair<? extends Employee>
子类型限定的通配符
表示任何泛型Pair类型,它的类型参数是Employee的子类。
Pair<? extends Employee>是Pair<Employee>和Pair<Manager>的超类,而Pair<Employee>和Pair<Manager>没有任何关系。
- ? extends Employee getFirst();
- void setFirst(? extends Employee);
Pair<? extends Employee>中某个访问器getFirst()方法的返回类型是
? extends Employee,它赋给一个Employee类型的对象是完全合法的。而setFirst方法,编译器只知道参数类型是Employee类或其子类,具体不知道,所以编译器拒绝传递任何特定的类型。
Pair<? super Employee>
超类型限定的通配符
表示任何泛型Pair类型,它的类型参数是Employee的超类。
Pair<? super Employee>是Pair<Employee>和Pair<Object>的超类。
- ? super Employee getFirst();
- void setFirst(? super Employee);
Pair<? super Employee>中某个访问器getFirst()方法的返回类型是
? super Employee,即Employee类型或其超类,只能把它赋给一个Object。
而更改器方法void setFirst(? super Employee),编译器虽然不知道其参数确切的类型,但是可以通过Employee类及其子类对象调用它(其实就是调用超类的方法)。
综上,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
PECS法则:
producer-extends,consumer-super。如果参数化类型表示一个生产者,就使用<? extends T>,如果他表示消费者,就使用<? super T>。
Pair<?>
无限定通配符
- ? getFirst();
- void setFirst(? );
getFirst方法仅能赋给Object类型的对象。setFirst方法不能被调用。