【Java集合框架】3 - 1 泛型
§3-1 泛型
3-1.1 泛型编程
泛型编程(generic programming),是一种编程思想。算法根据需要稍后指定的类型编写,在需要时,类型作为参数,提供特定类型的实例化算法。
Java 泛型(generics)是 JDK 5 所引入的一个新特性,提供了编译时类型安全检测机制。该机制允许程序员在编译时检测到非法的类型。
泛型的本质就是参数化类型,所操作的类型被指定为一个参数。这使得我们可以专注于算法设计本身,而不需要针对不同的数据类型,为相同的问题重写相同的算法。
例:编写一个可以针对任何数据类型的数组进行排序的算法。若用非泛型的方法,针对不同的数据类型,我们就要重写不同的方法,而这些方法的算法逻辑却是完全相同的。这时,使用泛型,就可以让我们专注于算法设计本身,而无需进行这些荣誉的操作。
Java 中的泛型标识符:
E
:Element,表示元素,在集合中使用(集合中存放的是元素);T
: Type,表示 Java 类;K
: Key,表示键;V
: Value,表示值;N
: Number,数值类型;?
:表示不确定的 Java 类型。
注意:一个泛型标识符对应着一个泛型类型参数,又称类型变量,用于指定一个泛型名称的标识符。类型参数只能代表引用数据类型,基本数据类型应当使用其对应包装类。
3-1.2 泛型方法
泛型方法允许在方法调用时接收不同类型的参数,根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
泛型方法定义规则:
- 方法声明的返回值类型之前必须有一个参数类型声明部分,由尖括号
<>
分隔,声明该方法为泛型方法; - 参数类型声明部分可包含一个或多个类型参数,参数间用逗号
,
分隔; - 类型参数可以用作声明返回值类型,也可用作声明实际参数的类型(占位符);
示例:打印数组(使用 Arrays.toString()
的实现方式)
// 泛型方法:打印数组
public static <E> String printArray(E[] arr) {
if (arr == null || arr.length == 0)
return "[]";
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; ; i++) {
sb.append(arr[i]);
if (i == arr.length - 1)
return sb.append("]").toString();
else
sb.append(", ");
}
}
有界类型参数:有时我们希望限制类型参数的类型,例如,操作数字的方法可能只需要接受 Number
或其子类实例,这是有界类型参数。
声明有界类型参数,列出类型参数后,接着 extends
关键字,其后跟着上限。注意,extends
在一般意义上表示扩展(类的继承)或实现(接口实现)。
使用有界类型参数,可允许调用边界中(类或接口)所定义的方法,请看下面的例子。
示例:使用有界类型参数,编写一个冒泡排序的泛型方法
// 泛型方法:冒泡排序
// extends 关键字表继承/实现接口
public static <T extends Comparable<T>> void bubbleSort(T[] arr) {
if (arr == null)
throw new NullPointerException();
if (arr.length == 0)
throw new ArrayIndexOutOfBoundsException();
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
//调用 Comparable 接口中的唯一方法
if (arr[j].compareTo(arr[j + 1]) > 0) {
T temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
多重界限:一个类型参数可以有多个边界,不同边界用 &
分隔。具有多个边界的类型变量是边界中列出的所有类型的子类型。
<T extends A & B & C>
3-1.3 泛型类
泛型类的声明类似于非泛型类。
泛型类声明方法:在类名后紧跟类型参数声明部分。
示例:声明一个泛型类
class Box<T> {
private T t;
public Box() {
}
public Box(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public String toString() {
return "Box{t = " + t + "}";
}
}
在测试类中,实例化该泛型类,需要在尖括号中指定参数类型,且前后都需要含有尖括号
//JDK 7 以前的实例化语句
Box<String> box = new Box<String>("Hello");
//JDK 7 以后的实例化语句:后半句中的实际类型可省略
Box<String> box = new Box<>();
3-1.4 类型通配符
类型通配符一般是使用 ?
代替具体的类型参数,表是不确定的 Java 类。
示例:List<?>
在逻辑上就是 List<String>
, List<Integer>
等所有 List<具体类型实参>
的父类。见下例中的 getData()
方法。
import java.util.ArrayList;
import java.util.List;
public class GenericsTest {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<String> strList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
intList.add(123);
strList.add("Hello world");
numList.add(3.14159);
getData(intList);
getData(strList);
getData(numList);
}
//? 类型通配符,不确定的Java类,所有具体类型实参的父类
//List<?> 类型参数,使得 intList, strList, numList 可入参
public static void getData(List<?> list) {
System.out.println("Data: " + list.get(0));
}
}
注意:
-
?
通配符也可以通过有界参数类型限定边界,方法相同(见前文); -
?
通配符可使用super
定义下限,例如<? super Number>
表示只能接受Number
及其上层父类类型;<? extends T>
表示该通配符所代表的类型是T
的子类(含自己);<? super T>
表示该通配符所代表的类型是T
的父类(含自己);可以使用通配符,结合继承体系,限定传入参数的类型的范围;
-
泛型不具备继承性,而数据具备继承性;
泛型不具备继承性:传递类型时只允许所指定的类型,其他类型均不接受;
public static method(ArrayList<Person> list) {...}
此时,传递其它类型的
ArrayList
则会出错,例如ArrayList<String>
等;数据具备继承性:传递数据时,仍可以通过多态与继承,传递不同类型的数据;
仍以上述方法为例,若
Person
具有一个子类Student
,在该ArrayList
中,可以添加其子类的数据:list.add(new Person("Zhang San", 20)); list.add(new Student("Li Si", 20)); list.add(new Student("Wang Wu", 20));
3-1.5 泛型接口
泛型接口就是带有类型参数声明的接口。
示例:Java 集合框架中声明了许多泛型接口,以 List
为例
public interface List<E> extends Collection<E> {...}
泛型接口的使用:
-
实现类中指定参数类型;
这种情况下,实现类中只能允许使用所指定的具体类型参数,且要重写其中方法;
public class MyList implements List<String> {...}
-
实现类中继续沿用泛型接口的类型参数;
这种情况下,实现类中的参数类型仍不确定,接口中的方法仍需要重写。
3-1.6 参考资料
[有界类型参数 | JAVA8 官网笔记教程 (zq99299.github.io)](