Java generic & wildcard
泛型类
泛型类的定义
class name<T1, T2, Tn> { /* ... */ }
Java 把上述 T1, T2, and Tn 称为泛型类型(Generic Types),或者类型参数(type parameters), 类型参数可以在类中任何地方使用。
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
泛型类的使用
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
泛型在使用时可以是任何的类、接口、数组、甚至是另一个泛型类。如上述的 T, T1, T2, and Tn 可以是 Integer, String, List
泛型接口
和泛型类一样,泛型接口也可以定义一个或多个类型参数,将类型参数置于接口名的后面。
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
其中 OrderedPair<K, V> 是泛型的定义,implements Pair<K, V> 中的 K 和 V 是泛型的使用,而不是定义,因为OrderedPair<K, V> 已经定义了 K 和 V。
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
泛型方法
泛型方法有自己声明的类型参数,这些类型参数的声明放在返回值之前。但泛型方法的类型参数的作用域只限于方法本身。适用于静态方法和实例方法以及泛型类的构造器。 可以被用到方法的参数声明、方法返回值、方法体中变量的声明和类型转换。 泛型方法使得该泛型方法的类型参数独立于类而产生变化。泛型方法和泛型类没有必然关系。
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
// 借助类型推断,可以省略类型参数
boolean same = Util.compare(p1, p2);
有界的类型参数
可以限制类型参数的范围,限制上界使用 extends 关键字。这里可以 extends 类和接口。
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
}
可以在类中使用泛型上界类的方法,如这里 Integer#intValue() 方法。
多个边界的类型参数
类型参数可以有多个上界,用 & 分隔。
<T extends A & B & C>
这里类型参数 T 拥有 A B C 三个上界,这三个上界可以是类,也可以是接口,如果是类的话,只有有一个类,其他的都是接口,且类必须放在第一个。
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ }
泛型方法中的多个边界
public static <T extends Comparable & Serializable> T max(T[] a) { ... }
通配符类型
无边界通配符 Unbounded Wildcards 通常用于不依赖类型参数的方法中,如下面的 printList 方法。
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
上界通配符 Upper Bounded Wildcards,
List<? extends Number> list = new ArrayList<Integer>();
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
下界通配符 Lower Bounded Wildcards
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
类型参数和通配符的区别
- 通配符有且仅有一个边界,而类型参数可以有多个边界。
- 通配符可以有一个上界或有一个下界,而类型参数不支持下界。
- 通配符不能在泛型类、泛型接口定义中使用,只能在泛型方法定义中使用。
类型参数和通配符的选择
在方法中可以同时使用类型参数和通配符
public <T> void func(List<? extends T> list, T t) {
list.add(t); // compile error 不可以修改 list
}
public <T> void func(List<? super T> list, T t) {
list.add(t); // 可以修改 list
}
public <T, E extends T> void func(List<E> list, T t){
list.add((E) t); // 可以修改,但是需要强制类型转换
}
通配符中上下界(extents、super) 的选择
遵循 PECS 原则,Producer Extends Consumer Super。
- 频繁往外读取内容的,适合用上界Extends,无法往里插入任何元素。
- 经常往里插入的,适合用下界Super,取出来的元素都是 Object 类型。
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}