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, 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。

    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));
    }

参考