一、简单泛型类
1.1 为什么要使用泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。使得程序具有更好的可读性和安全性。
1.2 定义简单泛型类
一个泛型类就是具有一个或多个类型变量的类。
public class Pair<T, U>{...}
private T first;
//类型变量使用大写形式且比较短,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值得类型。
Pair<String> ps = new Pair<Stirng>();
1.3 泛型方法
class ArrayAlg{
public static <T> T getMiddle(T... a){
return a[a.length / 2];
}
}
String middle = ArryAlg.<String>getMiddle("John", "Q.", "Public");
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");//可以省略,由编译器推断,但是有时候需要
//可以在普通类中定义泛型方法
1.4 类型变量的限定
有时候类或方法需要对类型变量加以约束。例如要计算泛型数组内最小元素,需要保证该类型有compareTo方法,可以通过限定实现了ComparaTo接口。
public static <T extends Comparable> T min(T[] a)...//C++则不能对模板参数类型加以限制。注意是Extend,意为绑定
T extends Comparable & Serializable //可以有多个绑定,绑定不只是接口,还可以是类,但是只能有一个类,且排在第一位
二、泛型代码和虚拟机
虚拟机没有泛型类型对象——所有对象都属于普通类。
2.1 类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。无限定用Object,有限定用第一个限定的类型变量。
2.2 翻译泛型表达式
编译器会插入强制类型转换。
2.3 翻译泛型方法
类方法中的泛型类型被擦除后会与多态发生冲突。编译器会在泛型类种生成一个桥方法
Java泛型转换的事实:
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都用它们的限定类型替换
- 桥方法被合成来保持多态
- 为保持类型安全性,必要时插入强制类型转换
2.4 遗留代码问题
@SuppressWarnings("unchecked")//这个注解会关闭对语句或方法中所有代码的检查
三、 约束与局限性
3.1主要阐述Java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的。
-
不能用基本类型实例化类型参数
-
运行时类型查询只适用于原始类型
a instanceof Pair<String>,getClass()// Pair.class,不会有具体的类型
-
不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10] // Error
-
Varargs警告
java不支持泛型类型的数组,单数可以向参数可变的方法传递一个泛型类型的实例。但是会得到一个警告。有两种方法可以抑制这个警告。//@SuppressWarnings("unchecked")//1 //@SafeVarags//2 public static<T> void addAll(Collection<T> coll, T...ts){ for(T t: ts) coll.add(t); }
-
不能实例化类型变量
即在泛型类或者方法内部不能使用new T()
-
不能构造泛型数组
public static <T extends Comparable> T[] minmax(T[] a) {T[] mm = new T[2] ;...} // Error,类型擦除
-
泛型类的静态上下文中类型变量无效
禁止使用带有类型变量的静态域和方法public class Singleton<T>{ private static T singleInstance; // Error,类型擦除后就剩下一个Object静态变量,不能为每个泛型生成一个静态变量 }
-
不能抛出或捕获泛型类的实例
泛型类不能拓展Throwable接口public class Problem<T> extends Exception {...} // Error
但是使用类型变量是允许的
publicstatic <T extends Throwable> void doWork(T t) throws T // OK
-
可以消除对受查异常的检查
@SuppressWarnings("unchecked")//这个注解会关闭对语句或方法中所有代码的检查
-
注意擦除后的冲突
3.2 泛型类型的继承规则
泛型与Java数组之间的重要区别。可以将Manager[]数组赋给一个类型为Employee[] 的变量。但泛型不行。
注意继承范例可以参照List与ArrayList。
3.3 通配符类型
-
概念,通配符类型中,允许类型参数变化。
Pair<? extends Employee>//表示泛型Pair类型,它的类型参数是Employee的子类,如Pair<Manager>,但不是Pair<String>
-
通配符的超类型限定
通配符限定与类型变量限定十分类似,但是,还有一个附加能力,可以指定一个超类型限定。
? super Manager//这个通配符限制为Manager的所有超类型
直观地讲,带有超类型限定的通配符可以向泛型对象写入,带有子类限定的通配符可以从泛型对象读取。 -
无限定通配符
Pair<?> ? getFirst() void setFirst(?)//Pair<?>和Pair本质不同在于,可以用任意Object对象调用原始Pair类的setObject方法 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); }
-
通配符捕获
交换元素时需要暂存中间变量,但是不能使用?
作为一种类型,因此需要用泛型来作为中间函数捕获。
四、反射与泛型
反射允许你在运行时分析任意的对象。如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除。