跳到主要内容

Java 泛型

本小节我们将学习 Java5 以后出现的一个特性:泛型(Generics。通过本小节的学习,你将了解到什么是泛型为什么需要泛型,如何使用泛型,如何自定义泛型,类型通配符等知识。

1. 什么是泛型

泛型不只是 Java 语言所特有的特性,泛型是程序设计语言的一种特性。允许程序员在强类型的程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须做出声明。

我们在上一小节已经了解到,Java 中的集合类是支持泛型的,它在代码中是这个样子的:

代码中的<Integer>就是泛型,我们把类型像参数一样传递,尖括号中间就是数据类型,我们可以称之为实际类型参数,这里实际类型参数的数据类型只能为引用数据类型。

那么为什么需要泛型呢?我们马上就见分晓。

2. 为什么需要泛型

上一节中,我们在使用ArrayList实现类的时候,如果没有指定泛型,IDEA会给出警告,代码似乎也是可以顺利运行的。请看如下实例:

import java.util.ArrayList;

public class GenericsDemo1 {

public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("str=" + str);
}

}

运行结果:

str=Hello

虽然运行时没有发生任何异常,但这样做有两个缺点:

  1. 需要强制类型转换: 由于ArrayList内部就是一个Object[]数组,在get()元素的时候,返回的是Object类型,所以在ArrayList外获取该对象,需要强制类型转换。其它的CollectionMap如果不使用泛型,也存在这个问题;
  2. 可向集合中添加任意类型的对象,存在类型不安全风险。例如如下代码中,我们向列表中既添加了Integer类型,又添加了String类型:
import java.util.ArrayList;

public class GenericsDemo2 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("element=" + str);
}
}

运行结果:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at GenericsDemo2.main(GenericsDemo2.java:8)

由于我们的“疏忽”,列表第 1 个元素实际上是整型,但被我们强制转换为字符串类型,这是行不通的,因此会抛出ClassCastException异常。

使用泛型可以解决这些问题。泛型有如下优点:

  1. 可以减少类型转换的次数,代码更加简洁;
  2. 程序更加健壮:只要编译期没有警告,运行期就不会抛出ClassCastException异常;
  3. 提高了代码的可读性:编写集合的时候,就限定了集合中能存放的类型。

3. 如何使用泛型

3.1 泛型使用

在代码中,这样使用泛型:

List<String> list = new ArrayList<String>();
// Java 7 及以后的版本中,构造方法中可以省略泛型类型:
List<String> list = new ArrayList<>();

要注意的是,变量声明的类型必须与传递给实际对象的类型保持一致,下面是错误的例子:

List<Object> list = new ArrayList<String>();
List<Number> numbers = new ArrayList(Integer);

3.2 自定义泛型类

3.2.1 Java 源码中泛型的定义

在自定义泛型类之前,我们来看下java.util.ArrayList是如何定义的:

类名后面的<E>就是泛型的定义,E不是 Java 中的一个具体的类型,它是 Java 泛型的通配符(注意是大写的,实际上就是Element的含义),可将其理解为一个占位符,将其定义在类上,使用时才确定类型。此处的命名不受限制,但最好有一定含义,例如java.lang.HashMap的泛型定义为HashMap<K,V>K表示KeyV表示Value

3.2.2 自定义泛型类实例1

下面我们来自定义一个泛型类,自定义泛型按照约定俗成可以叫<T>,具有Type的含义,实例如下: