泛型
整理BY:Misayaas
内容来自廖雪峰的官方网站: https://www.liaoxuefeng.com/
什么是泛型
泛型就是定义一种模板,例如ArrayList<T>
,然后在代码中为用到的类创建对应的ArrayList<类型>
:
1 | ArrayList<String> strList = new ArrayList<String>(); |
在Java标准库中的ArrayList<T>
实现了List<T>
接口,它可以向上转型为List<T>
:
1 | public class ArrayList<T> implements List<T> { |
要特别注意:不能把ArrayList<Integer>
向上转型为ArrayList<Number>
或List<Number>
==ArrayList
向上转型
使用泛型
使用ArrayList
时,如果不定义泛型类型时,泛型类型实际上就是Object
编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。例如,对于下面的代码:
1 | List<Number> list = new ArrayList<Number>(); |
编译器看到泛型类型List<Number>
就可以自动推断出后面的ArrayList<T>
的泛型类型必须是ArrayList<Number>
,因此,可以把代码简写为:
1 | // 可以省略后面的Number,编译器可以自动推断泛型类型: |
泛型接口
除了ArrayList<T>
使用了泛型,还可以在接口中使用泛型
Arrays.sort(Object[])
可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>
这个泛型接口:
1 | public interface Comparable<T> { |
String
本身已经实现了Comparable<String>
接口
1 | class Person implements Comparable<Person> { |
运行上述代码,可以正确实现按name
进行排序
编写泛型
把特定类型String
替换为T
,并申明<T>
:
1 | public class Pair<T> { |
静态方法
泛型类型<T>
不能用于静态方法
对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public 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 | public class Pair<T, K> { |
擦拭法
Java语言的泛型实现方式是擦拭法(Type Erasure)
- 所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的
Java使用擦拭法实现泛型,导致了:
- 编译器把类型
<T>
视为Object
; - 编译器根据
<T>
实现安全的强制转型
Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T
视为Object
处理,但是,在需要转型的时候,编译器会根据T
的类型自动为我们实行安全地强制转型
Java泛型的局限:
<T>
不能是基本类型,例如int
,因为实际类型是Object
,Object
类型无法持有基本类型:1
Pair<int> p = new Pair<>(1, 2); // compile error!
- 无法取得带泛型的
Class
- 无法判断带泛型的类型
1
2
3
4
5Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>) {
} - 不能实例化
T
类型
不恰当的覆写方法
有些时候,一个看似正确定义的方法会无法通过编译。例如:
1 | public class Pair<T> { |
换个方法名,避开与Object.equals(Object)
的冲突就可以成功编译:
1 | public class Pair<T> { |
泛型继承
一个类可以继承自一个泛型类
在继承了泛型类型的情况下,子类可以获取父类的泛型类型
extends通配符
假设我们定义了Pair<T>
:
1 | public class Pair<T> { ... } |
然后,我们又针对Pair<Number>
类型写了一个静态方法,它接收的参数类型是Pair<Number>
:
1 | public class PairHelper { |
上述代码是可以正常编译的。使用的时候,我们传入:
1 | int sum = PairHelper.add(new Pair<Number>(1, 2)); |
注意:传入的类型是Pair<Number>
,实际参数类型是(Integer, Integer)
使用Pair<? extends Number>
使得方法接收所有泛型类型为Number
或Number
子类的Pair
类型1
2
3
4
5
6
7
8
9
10
11
12public 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
了