简单泛型类
public class Pair<T> {
private T first;
private T second;
public Pair(){
first = null;
second = null;
}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
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;
}
}
提示:类型变量通常使用大写形式,且比较短。在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要进使用U和S)表示“任意类型”
泛型方法
class ArrayAlg{
public static <T> T getMiddle(T[] a){
return a[a.length / 2];
}
}
泛型方法可以定义在普通类中,也可以定义在泛型类中。
类型变量的限定
class ArrayAlg{
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a == null || a.length == 0)
return null;
T min = a[0];
T max = a[0];
for(int i = 1; i < a.length; i++){
if(min.compareTo(a[i]) > 0) min = a[i];
if(max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<T>(min, max);
}
}
以上代码中,将T限制为实现了Comparable接口的类。如:
public static void main(String[] args) {
Integer[] i = new Integer[]{12,345,563,24,64};
Pair<Integer> pair = ArrayAlg.minmax(i);
System.out.println(pair.getFirst() + " " + pair.getSecond());
}
如果将上面代码中第二行修改为int[] i = new int[]{12,345,563,24,64};,则第三行出现编译错误。
一个类型变量或通配符可以有多个限定,如:
T extends Comparable & Serializable
泛型代码和虚拟机
无论何时定义一个泛型类型,都自动提供原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型。
原始类型用第一个限定的类型变量来替换。如果没有给定限定就用Object替换。如Pair<T>中用Object替换 T,
注意:class Interval<Serializable & Comparable>中,原始类型用Serializable替换T,而编译器在必要时向Comparable插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界列表的末尾。
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。如:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst的返回类型后将返回Object类型。编译器自动插入Employee的强制类型转换。当存取一个泛型域时也要强制类型转换。如
Employee buddy = buddies.first;
方法public static <T extends Comparable> T min(T[] a)擦除后为public static Comparable min(Comparable[] a);
注意:方法擦除将带来两个复杂问题,请查看Java核心技术
Java泛型转换:
虚拟机中没有泛型 ,只有普通的类和方法
所有的类型参数都用到它们的限定类型替换
桥方法(查看Java核心技术)被合成保持多态
为保持类型安全性,必要时插入强制类型转换
约束与局限性
1.不能用基本类型实例化类型参数
只要8种基本类型,当包装类型不能接受替换时,可以使用独立的类和方法处理它们。
2.运行时类型查询只适用于原始类型
Pair<Integer> a = new Pair<Integer>();
a instanceof Pair<String>;
上面代码第2行将报错。
3.不能抛出也不能捕获泛型类实例
泛型类扩展Throwable都不合法。如public class Problem<T> extends Exception{ } //ERROR--不能通过编译。
不能在catch子句中使用类型变量,如
public static <T extends Throwable> void dowork(Class<T> t){
try{
do work
}
catch(T e){//编译错误 -- 不能捕获这种类型
}
}
但,在异常声明中可以使用类型变量,如:
public static <T extends Throwable> void doWork(T t) throws T{//OK
try{
do work
}
catch(Throwable realCause){
t.initCause(realCause);
throw t;
}
}
4.参数化类型的数组不合法
不能声明参数化类型的数组,如:Pair<String>[] table = new Pair<String>[10]; //ERROR
5.不能实例化类型变量
6.泛型类的静态上下文中类型变量无效
public class Singleton<T> {
public static T getSingInstance(){
}
}
上面代码中方法getSingInstance无法通过编译。
7.注意擦除后的冲突
public class Pair<T> {
public boolean equals(T value){
return first.equals(value)&&second.equals(value);
}
}
从概念上讲,Pair有两个equals方法:
boolean equals(T)//定义在Pair<T>
boolean equals(Object)//从Object中继承
方法擦除将导致两个方法相冲突。可以重新命名引发错误的方法。
通配符类型(用于方法参数)
通配符Pair<? extends Employee>表示任何任何Pair类型,它的类型参数是Employee的子类,如Pair<Manager>,但不能是Pair<String>
注意:类型Pair<Manager>是Pair<? extends Employee>的子类型,而ArrayList<Manager>并不是ArrayList<Employee>的子类
如:
public static void printBuddies(Pair<? extends Employee> p){可以这样调用 :printBuddies(new Pair<Manager>());
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first + " " + second);
}
超类型限定:? super Manager这个通配符限制为Manager的所有超类型
注意:当从超类型限定通配符中取出值时,只能将它赋给Object,如:Pair<? super Manager> p通配符中,Object o = p.getFirst();因为调用getFirst,返回的对象类型得不到保证。
直观地说,带有超类限定的通配符可以向泛型对象写入,如Pair<? super Manager> p中,可以这样调用:p.setFirst(new Manager());而带有子类型限定的通配符可以从泛型对象读取,如Pair<? extends Employee> p通配中,可以这样调用:Employee first = p.getFirst();
无限定通配符Pair<?>有方法:
? getFirst();
void setFirst();
而getFirst的返回值只能赋给一个Object,而setFirst方法不能被调用,甚至不能用Object调用。注意:Pair可以用任意Object对象调用原始的Pair类的setFirst方法。
无限定通配符对于很多简单的操作非常有用。如,下面这个方法将用来测试一个Pair是否包含了指定的对象,它不需要实际的类型
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
通配符捕获
有时我们需要使用通配符中的类型,如编写 一个交换Pair元素的方法public static void swap(Pair<?> p),但?不能作为一个类型,就是说
? t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
上面代码无法通过编译。
但我们可以通过一个辅助方法来完成工作,完整代码如下:
public static void swap(Pair<?> p){
swapHelper(p);
}
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
反射和泛型
Class类是泛型的。如String.class是Class<String>的唯一对象。类型参数十分有用。
public static <T> T createInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException{为了表达泛型类型声明,Java SE 5.0在java.lang.reflect包中提供了一个新的接口Type,包括
return clazz.newInstance();
}
Class类,描述具体类型
TypeVariable接口,描述类型变量
WildcardType接口,描述通配符
ParameterizedType接口,描述泛型类型或接口类型
GenericArrayType接口,描述泛型数组。
具体情况请查阅API