java 核心技术卷第八章:泛型程序设计
##1. 为什么要使用泛型程序设计
泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。
2. 定义简单泛型类
public class Pair<T>
{
private T first;
private T second;
public Pair() { first = null ; second = null ; }
public PairfT first , T second) { this,first = first; this.second = second; }
public T getFirstO { return first; }
public T getSecondO { return second; }
public void setFirst (T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
3. 泛型方法
class ArrayAlg
{
public static <T> T getMiddle(T... a)
{
return a[a.length / 2];
}
}
String middle = ArrayAlg.<String>getMiddle("]ohnM, "Q.n, "Public");
4. 类型变量的限定
public static <T extends Coiparab1e> T min(T[] a) . . .
下面的记法<T extends BoundingType〉表示T 应该是绑定类型的子类型(subtype)。T 和绑定类型可以是类, 也可以是接口。选择关键字extends 的原因是更接近子类的概念, 并且Java 的设计者也不打算在语言中再添加一个新的关键字(如sub)。一个类型变量或通配符可以有多个限定,如:T extends Comparable & Serializable
5. 泛型代码和虚拟机
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用Object)。如Pair 原始类型,因为T 是一个无限定的变量, 所以直接用Object 替换。
public class Pair
{
private Object first;
private Object second;
public Pair(Object first, Object second)
{
this,first = first;
this.second = second;
}
public Object getFirstO { return first; }
public Object getSecondO { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}
原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用Object 替换。
- 当程序调用泛型方法时, 如果擦除返回类型, 编译器插入强制类型转换。
Pair<Employee> buddies = . .
Employee buddy = buddies.getFirst();
擦除getFirst 的返回类型后将返回Object 类型。编译器自动插人Employee 的强制类型转换。
需要记住有关Java 泛型转换的事实:
- 虚拟机中没有泛型, 只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态。
- 为保持类型安全性,必要时插人强制类型转换。
6. 约束与局限性
- 不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此, 没有Pair, 只有Pair。当然,其原因是类型擦除。擦除之后, Pair 类含有Object 类型的域, 而Object 不能存储double 值。
- 运行时类型查询 只适用于原始类型
if (a instanceof Pair<String>) // Error
if (a instanceof Pair<T>) // Error
试图查询一个对象是否属于某个泛型类型时, 倘若使用instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
Pair <String> stringPair = . .
Pair < Employee〉employeePair = . .
if (stringPair.getClassO == employeePair .getClassO) // they are equal 这是因为两次调用getClass 都将返回Pair.class。
- 不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10] ; // Error
可以通过类型ArrayList来克服这一个困难
-
Varargs 警告
向参数个数可变的方法传递一个泛型类型的实例。考虑下面这个简单的方法, 它的参数个数是可变的:
public static <T> void addAll(Collections coll, T... ts) { for (t : ts) coll.add⑴; }
为了调用这个方法,Java 虚拟机必须建立一个Pair 数组, 这就违反了前面的规则。不过,对于这种情况, 规则有所放松, 你只会得到一个警告,而不是错误。可以采用两种方法来抑制这个警告。一种方法是为包含addAll 调用的方法增加注解@SuppressWamings(“unchecked”)。 或者在Java SE 7 中, 还可以用SafeVarargs 直接标注addAll 方法:
@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)
- 不能实例化类型变量
不能使用像new T(…),newT[…] 或T.class 这样的表达式中的类型变量
-
不能构造泛型数组
-
泛型类的静态上下文中类型变量无效
-
不能抛出或捕获泛型类的实例
-
注意擦除后的冲突
catch (T e) // Error can't catch type variable
7. 泛型类型的继承规则
泛型类可以扩展或实现其他的泛型类。
8. 通配符类型
- 通配符类型中, 允许类型参数变化。例如, 通配符类型
Pair<? extends Employee>
public static void printBuddies(Pair <Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecondO;
Systefn.out.println(first.getName() + " and " + second.getNameQ + " are buddies.");
}
但是不能将Pair传递给这个方法!但是当方法改为public static void printBuddies(Pair<? extends Eiployee> p)
但是在使用wi1dcardBuddies.setFirst(1owlyEnployee) ; // compile-time error
会出现编译时错误。这样将不可能调用setFirst 方法。编译器只知道需要某个Employee 的子类型, 但不知道具体是什么类型。它拒绝传递任何特定的类型。
- 通配符的超类型限定
Pair <? super Manager>
只能传递Manager 类型的对象, 或者某个子类型(如Executive) 对象。另外, 如果调用getFirst , 不能保证返回对象的类型。只能把它赋给一个Object。
- 无限定通配符
Pair<?>
?getFirst()
void setFirst(?)
Pair<?> 和Pair 本质的不同在于: 可以用任意Object 对象调用原始Pair 类的setObject方法。
- 通配符捕获
编写一个交换成对元素的方法:
public static void swap(Pair<?> p)
通配符不是类型变量, 因此, 不能在编写代码中使用“ ?” 作为一种类型。但是可以通过swaphelper来进行操作。
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
public static void swap(Pair<?> p) { swapHelper(p) ; }
9. 反射和泛型
Java 泛型的卓越特性之一是在虚拟机中泛型类型的擦除。
可以通过反射的方式来获取泛型中的相关类型等必要信息,具体方法可参考反射的具体api