# 1. 泛型

泛型就是编写模板代码来适应任意类型;

泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;

注意泛型的继承关系:可以把ArrayList<Integer>向上转型为List<Integer>T不能变!),但不能把ArrayList<Integer>向上转型为ArrayList<Number>T不能变成父类)。

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

# 2. 使用泛型

  1. 使用泛型时,把泛型参数<T>替换为需要的class类型,例如:ArrayList<String>ArrayList<Number>等;
  2. 可以省略编译器能自动推断出的类型,例如:List<String> list = new ArrayList<>();
  3. 不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型;
  4. 可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。

Person实现Comparable<T>接口:

class Person implements Comparable<Person> {
    String name;
    int score;
    Person(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
    public String toString() {
        return this.name + "," + this.score;
    }
}

# 3. 编写泛型

public class Pair<T>{
  private T first;
  private T last;
  
  public Pair(T first, T last) {
    this.first = first;
    this.last = last;
  }
  
  public T getFirst{
    return first;
  }
  public T getLast{
    return last;
  }
  
  // 静态方法
  public static <K> Pair<K> create(K first, K last) {
    return new Pair(K first, K last)
  }
}

多个泛型类型:

public class Pair<T, K> {
    private T first;
    private K last;
    public Pair(T first, K last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public K getLast() { ... }
}

使用的时候,需要指出两种类型:

Pair<String, Integer> p = new Pair<>("test", 123);

小结:

  1. 编写泛型时,需要定义泛型类型<T>
  2. 静态方法不能引用泛型类型<T>,必须定义其他类型(例如<K>)来实现静态泛型方法;
  3. 泛型可以同时定义多种类型,例如Map<K, V>

# 4. 擦拭法

擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

  • 编译器把类型<T>视为Object
  • 编译器根据<T>实现安全的强制转型。

Java泛型的局限:

  • 不能是基本类型,例如:int,因为实际类型是ObjectObject类型无法持有基本类型;

  • 不能获取带泛型类型的Class,例如:Pair<String>.class

    • 因为,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>
  • 不能判断带泛型类型的类型,例如:x instanceof Pair<String>

    • 因为,并不存在Pair<String>.class,而是只有唯一的Pair.class
  • 不能实例化T类型,例如:new T()

    • 擦拭后实际上变成了:
    first = new Object();
    last = new Object();
    

    这样一来,创建new Pair<String>()和创建new Pair<Integer>()就全部成了Object,显然编译器要阻止这种类型不对的代码

泛型方法要防止重复定义方法,例如:public boolean equals(T obj)

# 5. 泛型 extends 通配符

# 5.1. extends通配符的作用

如果我们考察Java标准库的java.util.List<T>接口,它实现的是一个类似“可变数组”的列表,主要功能包括:

public interface List<T> {
    int size(); // 获取个数
    T get(int index); // 根据索引获取指定元素
    void add(T t); // 添加一个新元素
    void remove(T t); // 删除一个已有元素
}

现在,让我们定义一个方法来处理列表的每个元素:

int sumOfList(List<? extends Integer> list) {
    int sum = 0;
    for (int i=0; i<list.size(); i++) {
        Integer n = list.get(i);
        sum = sum + n;
    }
    return sum;
}

为什么我们定义的方法参数类型是List<? extends Integer>而不是List<Integer>?从方法内部代码看,传入List<? extends Integer>或者List<Integer>是完全一样的,但是,注意到List<? extends Integer>的限制:

  • 允许调用get()方法获取Integer的引用;
  • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。

因此,方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法(恶意调用set(null)除外)。

小结:

使用类似<? extends Number>通配符作为方法参数时表示:

  • 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();
  • 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);

即一句话总结:使用extends通配符表示可以读,不能写。

使用类似<T extends Number>定义泛型类时表示:

  • 泛型类型限定为Number以及Number的子类。

# 6. 泛型 super 通配符

使用<? super Integer>通配符表示:

  • 允许调用set(? super Integer)方法传入Integer的引用;
  • 不允许调用get()方法获得Integer的引用。

唯一例外是可以获取Object的引用:Object o = p.getFirst()

即使用super通配符表示只能写不能读。

# 6.0.1. PECS 原则

使用extendssuper通配符要遵循PECS原则,Producer Extends Consumer Super

即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

Collectionscopy()方法为例:

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i); // src是producer
            dest.add(t); // dest是consumer
        }
    }
}

需要返回Tsrc是生产者,因此声明为List<? extends T>,需要写入Tdest是消费者,因此声明为List<? super T>

# 6.0.2. 无限定通配符<?>

无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类。

因为<?>通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

换句话说,既不能读,也不能写,那只能做一些null判断:

static boolean isNull(Pair<?> p) {
    return p.getFirst() == null || p.getLast() == null;
}

# 7. 泛型和反射

Java的部分反射API也是泛型。例如:Class<T>就是泛型:

// compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();

// no warning:
Class<String> clazz = String.class;
String str = clazz.newInstance();

调用ClassgetSuperclass()方法返回的Class类型是Class<? super T>

Class<? super String> sup = String.class.getSuperclass();

构造方法Constructor<T>也是泛型:

Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);

我们可以声明带泛型的数组,但不能用new操作符创建带泛型的数组:

Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!

必须通过强制转型实现带泛型的数组:

@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];

小结:

  1. 部分反射API是泛型,例如:Class<T>Constructor<T>
  2. 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
  3. 可以通过Array.newInstance(Class<T>, int)创建T[]数组,需要强制转型;
  4. 同时使用泛型和可变参数时需要特别小心。