泛型 - 廖雪峰的官方网站 (liaoxuefeng.com)

什么是泛型

泛型就是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>

向上转型

类型ArrayList<T>可以向上转型为List<T>T不能变成父类)

特别注意:不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

使用泛型

1
2
// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<Number> list = new ArrayList<>();

泛型接口

在接口中使用泛型

Comparable<T>泛型接口:

1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}

在使用的时候实现这个接口就可以比较对应的类型了

编写泛型

泛型类型<T>不能用于静态方法

对于静态方法,我们可以单独改写为**“泛型”方法**,只需要使用另一个类型即可

1
2
public static <K> Pair<K> create(K first, K last){}
//写成K只是为了区分,事实上写为T也没问题

多个泛型类型

泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>

Java标准库的Map<K, V>就是使用两种泛型类型的例子。

擦拭法

Java语言的泛型实现方式是擦拭法(Type Erasure)。

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

擦拭法决定了泛型<T>

  • 不能是基本类型,例如:int;(只能是Integer

    • 因为实际类型是ObjectObject类型无法持有基本类型

      1
      Pair<int> p = new Pair<>(1, 2); // compile error!
  • 不能获取带泛型类型的Class,例如:Pair<String>.class

    • 并不存在Pair<String>.class,而是只有唯一的Pair.class
  • 不能判断带泛型类型的类型,例如:x instanceof Pair<String>

    • 同上
  • 不能实例化T类型,例如:new T()

泛型方法要防止重复定义方法,例如:public boolean equals(T obj),编译器会阻止一个实际上会变成覆写的泛型方法定义。

子类可以获取父类的具体的泛型类型<T>

1
2
3
4
5
public class IntPair extends Pair<Integer> {
}

//调用
IntPair ip = new IntPair(1, 2);//可以直接写int参数

extends通配符

使用Pair<? extends Number>使得方法接收所有泛型类型为NumberNumber子类的Pair类型。

这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。

只能读不能写

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

super通配符

Pair<? super Integer>表示,方法参数接受所有泛型类型为IntegerInteger父类的Pair类型。

使用<? super Integer>通配符表示:只能写不能读

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

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

换句话说,使用<? super Integer>通配符作为方法参数,表示方法内部代码对于参数只能写,不能读。

1
2
3
4
5
6
7
8
9
public class Collections {
// 把src的每个元素复制到dest中:
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);// t是producer, src extends
dest.add(t);// t是consumer, dest extends
}
}
}

PECS原则

Producer Extends Consumer Super

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

无限定通配符

1
2
void sample(Pair<?> p) {
}

既不能读,也不能写,那只能做一些null判断

很少使用,可以用<T>替换,同时它是所有<T>类型的超类。

泛型和反射

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

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

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

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

安全使用泛型数组

使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps,因为它的类型是泛型数组。但是,编译器不会检查变量arr,因为它不是泛型数组。因为这两个变量实际上指向同一个数组,所以,操作arr可能导致从ps获取元素时报错,例如,以下代码演示了不安全地使用带泛型的数组:

1
2
3
4
5
6
7
8
9
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;

ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);

// ClassCastException:
Pair<String> p = ps[1];
String s = p.getFirst();

要安全地使用泛型数组,必须扔掉arr的引用:

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

不能直接创建泛型数组T[],因为擦拭后代码变为Object[]

必须借助Class<T>来创建泛型数组

1
2
3
T[] createArray(Class<T> cls) {
return (T[]) Array.newInstance(cls, 5);
}

我们还可以利用可变参数创建泛型数组T[]

1
2
3
4
5
6
7
8
9
public class ArrayHelper {
@SafeVarargs
static <T> T[] asArray(T... objs) {
return objs;
}
}

String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);

谨慎使用泛型可变参数

后记

泛型好难懂

感觉简而言之不要用x