泛型程序设计意味着代码可被不同类型的对象所重用
通配符(wildcard type),使用大写。在Java苦衷,使用变量E表示集合的元素类型,K和V分别表示表的关键字和值的类型,T/U/S表示“任意类型”
泛型类
可以看作普通类的工厂。
public class Pair {
private T min;
private T max;
public Pair() {
min = null;
max = null;
}
public Pair(T min, T max) {
this.min = min;
this.max = max;
}
public T getMin() {return min; }
public void setMin(T min) {this.min = min;}
public T getMax() {return max;}
public void setMax(T max) {this.max = max;}
}
泛型方法
类型变量放在修饰符之后,返回类型之前。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
public static T getMiddle(T... a) {
return a[a.length / 2];
}
声明泛型类型调用:
System.out.println(ArrayAlg.
未声明泛型类型调用:
System.out.println(ArrayAlg. getMiddle(1.0, 2, 3));// 注意这里是1.0,2,3,自动封装为Number类型。
多类型调用:
System.out.println(ArrayAlg. getMiddle("hello", 2, 3));
类型变量的限定
对类/方法的类形变量加以约束。通过对类型变量 T 设置限定(bound) 实现。例如下面这段代码,要获取最小值,需要保证T实现了Comparable接口关键字extends
public static T min(T[] a){
if (a == null || a.length == 0)
return null;
T smallest = a[0];
for (T num : a){
if (smallest.compareTo(num) > 0)
smallest = num;
}
return smallest;
}
为什么不是implements呢?记法
(1)表示T应该是绑定类型的子类型,T和绑定类型可以是类,也可以是接口。选择关键字 extends 的原因是更接近子类的概念。
(2)多个限定类型,用“&”分隔:
T extends Comparable & Serializable
(3)多个类型变量,用“,”分割
(4)可以拥有多个接口超类型, 但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个
泛型代码和虚拟机
虚拟机没有泛型类型对象——所有对象都是普通类。
类型擦除
定义的泛型类型,都会自动提供一个相应的原始类型(raw type)。原始类型名即删掉泛型类型后的名字。擦除类型变量,并替换为限定类型(无限定类型的变量用Object)。例如Pair
public class Pair {
private Object min;
private Object max;
public Pair() {
min = null;
max = null;
}
public Pair(Object min, Object max) {
this.min = min;
this.max = max;
}
public Object getMin() {return min; }
public void setMin(Object min) {this.min = min;}
public Object getMax() {return max;}
public void setMax(Object max) {this.max = max;}
}
有限定类型的情况:
public class Interval implements Serializable{
private T lower;
private T upper;
public Interval (T first, T second){
if (first.compareTo(second) <= 0) {
lower = first;
upper = second;}
else {
lower = second;
upper = first;
}
}
}
原始类型Interval,如下:
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
public Interval (Comparable first, Comparable second){
if (first.compareTo(second) <= 0) {
lower = first;
upper = second;}
else {
lower = second;
upper = first;
}
}
}
切换限定class Interval
编译器在必要时要向 Comparable 插入强制类型转换。为了提高效率, 应该将标签(tagging) 接口(即没有方法的接口)放在边界列表的末尾。
翻译泛型表达式:
擦除getFirst方法的返回类型,并返回Object类型的情况:
Pair buddies = . . .;
Employee buddy = buddies.getFirst();
编译器自动插入Employee的强制类型转换。
翻译泛型方法
擦除类型参数,留下限定类型的情况:
public static
同时在Pair中继承到的setSecond方法为:
public void setSecond(Object second)
考虑下列语序:
DateInterval interval = new DateInterval(...);
Pair pair = interval;
pair.setSecond(new LocalDate());
需要对setSecond的调用具有多态性。那么需要编译器在DateInterval 类中生成一个桥方法。
public void setSecond(Object second) {
setSecond((Date) second);
}
即实际上调用的为DateInterval.setSecond(Date)方法,这个方法是合成桥方法。
注意:
- VM中没有泛型,只有普通的类和方法
- 所有的类型参数都用他们的限定类型/Object替换
- 桥方法被合成来保持多态
- 为保持类型安全性,必要时插入强制类型转换。
约束及局限性
-
不能用基本类型代替类型参数
Pair成立,Pair 不成立。
原因:类型擦除。擦除之后,Pair含有Object类型的域。 而 Object 不能存储 double 值。
-
检查类型只适用于原始类型
即检查mm是否是任意类型的一个Pair。同样的道理, getClass 方法总是返回原始类型if (mm instanceof Pair){ // 写成Pair
Pair 均会报错 System.out.println("yes"); }
- 不能创建参数化类型的数组
可以声明通配类型的数组, 然后进行类型转换
Pair[] pair0 = new Pair[10]; // ERROR
Pair[] pair = (Pair[]) new Pair>[10];// CORRECT
- 不能构造泛型数组
如下:直接构造数组实例是会报错的。通过强制转换方式,运行时当Object[]引用给Comparable[]变量时,会发生类转换异常
T[] mm = new T[2]; // ERROR
...改造为
public static T[] minmax(T... a){
T[] mm = (T[]) new Object[2]; // Compiles with warning,
mm[0] = a[0];
mm[1] = a[0];
for (T word : a) {
if (mm[0].compareTo(word) > 0)
mm[0] = word;
if (mm[1].compareTo(word) < 0)
mm[1] = word;
}
return mm;
}
调整minmax方法为:
public static T[] minmax(IntFunction constr,T... a){
// minmax 方法使用这个参数生成一个有正确类型的数组
T[] mm = constr.apply(2);
...
}
...
String[] strings = Pair.minmax(String[]::new,"Hello","How","Are","You");
- Varargs警告
既然Java 不支持泛型类型的数组,我们传递一个泛型类型数组实例:
public static void addAll(Collection collection, T... ts) {
for (T t : ts) {
collection.add(t);
}
}
为addAll方法添加下列注解之一即可:
@SafeVarargs
@SuppressWarnings("unchecked")
- 不能实例化类型变量
即 new T();是不存在的,本意是不希望调用new Object()的,利用Supplier优化:
public static Pair makePair(Supplier constr) {
return new Pair<>(constr.get(), constr.get());
}
...
Pair pair1 = Pair.makePair(String::new);
既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 都是不合法的。
注意擦除后的冲突。要想支持擦除的转换, 就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类, 而这两个接口是同一接口的不同参数化。
继承规则
问:Pair
答:不是。
无论S和T有什么联系,通常, Pair 与 Pair
泛型类可以扩展或实现其他的泛型类。就这一点而言,与普通的类没有什么区别。 例如, ArrayList
通配符类型
1.子类型限定。
Pair extends Employee>
表示任何泛型 Pair 类型,类型参数是 Employee 的子类,如Pair
2.超类型限定
? super Manager
这个通配符限制为 Manager 的所有超类型。
例如:
计算一个 String 数组的最小值,T 就是 String类型的, 而 String 是Comparable
public static > min(T[] a)...
处理一个 LocalDate 对象的数组时, 会出现一个问题。
LocalDate 实现了 ChronoLocalDate, 而 ChronoLocalDate 扩展了 Comparable
public static > T min(T[] a) ...
int compareTo(? super T)
3.无限定通配符
Pair>
Pair> 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。一般用来测试一个Pair 是否包含一个null引用,不需要实际的类型。
反射和泛型
- 泛型Class类,Class
。例如String.class就是一个Class 类的对象 - 使用 Class
参数进行类型匹配。例如下例执行makePair(Employee.class)
public static Pair makePair(Class tClass) throws InstantiationException,IllegalAccessException{
return new Pair<>(tClass.newInstance(), tClass.newInstance());
}
- 虚拟机中的泛型类型信息
public static >T min(T[] a)
... 擦除后
public static Comparable min(Comparable[] a)
使用反射 API 来确定:
- 有一个T的类型参数。
- T有一个子类型限定。
- 限定类型有一个通配符参数。?
- 通配符参数有一个超类限定。 super T>
- 有一个泛型数组参数。T[]