泛型

整理BY:Misayaas

内容来自廖雪峰的官方网站: https://www.liaoxuefeng.com/

什么是泛型

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

1
ArrayList<String> strList = new ArrayList<String>();

在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>

1
2
3
4
5
public class ArrayList<T> implements List<T> {
...
}

List<String> list = new ArrayList<String>();

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

==ArrayList和ArrayList两者完全没有继承关系==

向上转型

使用泛型

使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object

编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。例如,对于下面的代码:

1
2
List<Number> list = new ArrayList<Number>();

编译器看到泛型类型List<Number>就可以自动推断出后面的ArrayList<T>的泛型类型必须是ArrayList<Number>,因此,可以把代码简写为:

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

泛型接口

除了ArrayList<T>使用了泛型,还可以在接口中使用泛型

Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口:

1
2
3
4
5
6
7
8
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
  • String本身已经实现了Comparable<String>接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

运行上述代码,可以正确实现按name进行排序

编写泛型

把特定类型String替换为T,并申明<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

静态方法

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() { ... }
public T getLast() { ... }

// 静态泛型方法应该使用其他类型区分:
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
}

多个泛型类型

我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>

1
2
3
4
5
6
7
8
9
10
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() { ... }
}

擦拭法

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

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

Java使用擦拭法实现泛型,导致了:

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

Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型

Java泛型的局限:

  1. <T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型:
    1
    Pair<int> p = new Pair<>(1, 2); // compile error!
  2. 无法取得带泛型的Class
  3. 无法判断带泛型的类型
    1
    2
    3
    4
    5
    Pair<Integer> p = new Pair<>(123, 456);
    // Compile error:
    if (p instanceof Pair<String>) {
    }

  4. 不能实例化T类型

不恰当的覆写方法

有些时候,一个看似正确定义的方法会无法通过编译。例如:

1
2
3
4
5
6
public class Pair<T> {
public boolean equals(T t) {
return this == t;
}
}

换个方法名,避开与Object.equals(Object)的冲突就可以成功编译:

1
2
3
4
5
public class Pair<T> {
public boolean same(T t) {
return this == t;
}
}

泛型继承

一个类可以继承自一个泛型类

在继承了泛型类型的情况下,子类可以获取父类的泛型类型

extends通配符

假设我们定义了Pair<T>

1
2
public class Pair<T> { ... }

然后,我们又针对Pair<Number>类型写了一个静态方法,它接收的参数类型是Pair<Number>

1
2
3
4
5
6
7
8
public class PairHelper {
static int add(Pair<Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}

上述代码是可以正常编译的。使用的时候,我们传入:

1
2
int sum = PairHelper.add(new Pair<Number>(1, 2));

注意:传入的类型是Pair<Number>,实际参数类型是(Integer, Integer)

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

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}

static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}

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