Java泛型
Free Talk
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
廖雪峰、菜鸟
什么是泛型?
在讲解什么是泛型之前,我们先观察Java标准库提供的ArrayList
,它可以看作“可变长度”的数组,因为用起来比数组更方便。
实际上ArrayList
内部就是一个Object[]
数组,配合存储一个当前分配的长度,就可以充当“可变数组”:
1 | public class ArrayList { |
如果用上述ArrayList
存储String
类型,会有这么几个缺点:
- 需要强制转型;
- 不方便,易出错。
例如,代码必须这么写:
1 | ArrayList list = new ArrayList(); |
为了解决这个问题,我们必须把ArrayList
变成一种模板:ArrayList<T>
,代码如下:
1 | public class ArrayList<T> { |
这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList
:
1 | // 创建可以存储String的ArrayList: |
因此,泛型就是定义一种模板,例如ArrayList<T>
,然后在代码中为用到的类创建对应的ArrayList<类型>
,由编译器针对类型作检查。
小结
-
泛型就是编写模板代码来适应任意类型;
-
泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
-
注意泛型的继承关系:可以把
ArrayList<Integer>
向上转型为List<Integer>
(T不能变!),但不能把ArrayList<Integer>
向上转型为ArrayList<Number>
(T不能变成父类)。
使用泛型
使用ArrayList
时,如果不定义泛型类型时,泛型类型实际上就是Object
。
当我们定义泛型类型<String>
后,List<T>
的泛型接口变为强类型List<String>
:
1 | // 无编译器警告: |
编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。例如,对于下面的代码:
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
数组进行排序:
1 | // sort |
这是因为String
本身已经实现了Comparable<String>
接口。如果换成我们自定义的Person
类型就会得到ClassCastException
,即无法将Person
转型为Comparable
。我们修改代码,让Person
实现Comparable<T>
接口:
1 | // sort |
通配符
- ? 表示不确定的 Java 类型
- T (type) 表示具体的一个 Java 类型
- K V (key value) 分别代表 Java 键值中的Key Value
- E (element) 代表Element
小结
-
使用泛型时,把泛型参数
<T>
替换为需要的class
类型,例如:ArrayList<String>,ArrayList<Number>
等; -
可以省略编译器能自动推断出的类型,例如:
List<String> list = new ArrayList<>();
; -
不指定泛型参数类型时,编译器会给出警告,且只能将
<T>
视为Object
类型; -
可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型