Java 泛型
文章目录
在 Java 反射中,Class
类和 Constructor
类后都有 <?>
的标识,这个标识表示“任意类型”,即泛型。通过这个标识,一个 Class
类可以存储任何类型的 Class
对象。
泛型的定义与使用
泛型标记符
上面提到的 <?>
中的 ?
是一种常用的标记符。Java 中有如下常用标记符:
标记符 | 含义 |
---|---|
E |
Element - 在集合中使用,表示集合元素 |
T |
Type - 类型 |
K |
Key - 键 |
V |
Value - 值,常与 K 搭配使用 |
N |
Number - 数值 |
? |
无法确定类型 |
提示
上面提到的标记符除 ?
外均为命名惯例,即使把代码中所有的 T
都改成 D
甚至 GDN
,代码也是符合命名规范的。
泛型类
泛型类是指具有一个或多个类型参数的类。
1package com.jackgdn.entities;
2
3import java.util.Optional;
4
5public class Box<T> { // 泛型类 Box,T 是类型参数
6 private T content; // Box 类中 content 属性的类型需要在实例化时指定
7
8 public Box() {
9 this.content = null;
10 }
11
12 public Box(T content) {
13 this.content = content;
14 }
15
16 public void setContent(T content) {
17 this.content = content;
18 }
19
20 public T getContent() { // 该方法的返回值类型是 T,即 Box 中存储的内容的类型
21 return content;
22 }
23
24 public String getTypeName() {
25 return Optional.ofNullable(content)
26 .map(c -> c.getClass())
27 .map(c -> c.getName())
28 .orElse(null);
29 }
30}
1package com.jackgdn;
2
3import com.jackgdn.entities.Box;
4
5public class Main {
6 public static void main(String[] args) {
7 Box<String> box = new Box<>("Hello, World!");
8 System.out.println("Content: " + box.getContent());
9 System.out.println("Type Name: " + box.getTypeName());
10 }
11}
12
13/*
14 * output:
15 * Content: Hello, World!
16 * Type Name: java.lang.String
17 */
泛型方法
泛型方法是指在方法声明中定义了类型参数的方法。
1package com.jackgdn.entities;
2
3public class Sorter {
4 public static <T extends Comparable<T>> void sort(T[] array) { // 上限通配符,表示 T 必须实现 Comparable 接口
5 if (array == null || array.length < 2) {
6 return;
7 }
8
9 for (int i = 0; i < array.length - 1; i++) { // 使用冒泡排序算法
10 for (int j = 0; j < array.length - 1 - i; j++) {
11 if (array[j].compareTo(array[j + 1]) > 0) {
12 T temp = array[j];
13 array[j] = array[j + 1];
14 array[j + 1] = temp;
15 }
16 }
17 }
18 }
19}
1package com.jackgdn;
2
3import java.util.Arrays;
4
5import com.jackgdn.entities.Sorter;
6
7public class Main {
8 public static void main(String[] args) {
9 Integer[] integers = { 5, 3, 5, 8, 9, 7, 9, 3, 2, 3 };
10 Sorter.sort(integers);
11 System.out.println(Arrays.toString(integers));
12
13 String[] strings = { "Hello", "World", "Java", "Generics" };
14 Sorter.sort(strings);
15 System.out.println(Arrays.toString(strings));
16 }
17}
18
19/*
20 * output:
21 * [2, 3, 3, 3, 5, 5, 7, 8, 9, 9]
22 * [Generics, Hello, Java, World]
23 */
Sorter.sort
方法中 <T extends Comparable<T>>
是一个上限通配符,一般写法是 <? extends T>
,表示 ?
类型是 T
类型的子类/实现了 T
接口;与之对应的是下限通配符,一般写法是 <? super T>
,表示 ?
类型是 T
的祖先类。
泛型接口
1package com.jackgdn.interfaces;
2
3// 泛型接口
4public interface Pair<K, V> {
5
6 K getKey();
7
8 V getValue();
9
10 void setKey(K key);
11
12 void setValue(V value);
13
14}
1package com.jackgdn.entities;
2
3import com.jackgdn.interfaces.Pair;
4
5import lombok.*;
6
7@Data
8@NoArgsConstructor
9@AllArgsConstructor
10// 实现 Pair 泛型接口
11public class OrderedPair<K, V> implements Pair<K, V> {
12 private K key;
13 private V value;
14}
1package com.jackgdn;
2
3import com.jackgdn.entities.OrderedPair;
4import com.jackgdn.interfaces.Pair;
5
6public class Main {
7 public static void main(String[] args) {
8 // 创建键为 String,值为 Double 的 Pair
9 Pair<String, Double> pair1 = new OrderedPair<>("PI", 3.14);
10
11 // 创建键为 Integer,值为 Boolean 的 Pair
12 Pair<Integer, Boolean> pair2 = new OrderedPair<>(null, null);
13 pair2.setKey(1);
14 pair2.setValue(true);
15
16 System.out.println(pair1);
17 System.out.println(pair2);
18 }
19}
20
21/*
22 * output:
23 * OrderedPair(key=PI, value=3.14)
24 * OrderedPair(key=1, value=true)
25 */
类型擦除
Java 泛型是通过类型擦除实现的。在编译时,所有泛型类型信息都会被擦除:
- 如果泛型类型没有边界(如
<T>
),则所有的T
都会被替换为Object
- 如果泛型类型有边界(如
<T extends Comparable<T>>
),则所有的T
都会被替换为Comparable
以 OrderedPair
中的例子来说,当通过 Pair<String, Double> pair1 = new OrderedPair<>("PI", 3.14);
创建 pair1
之后,pair1
中的所有 K
都会变为 String
类型,所有 V
都会变为 Double
类型。
1package com.jackgdn;
2
3import java.lang.reflect.Constructor;
4import java.util.Arrays;
5
6import com.jackgdn.entities.Box;
7
8public class Main {
9 public static void main(String[] args) {
10 Box<String> stringBox = new Box<>("GDN");
11 Class<?> boxClass = stringBox.getClass(); // 获取 stringBox 的运行时类型
12
13 try {
14 // stringBox 中已经发生类型擦除,其含参构造方法的参数被指定为了 Object 类型
15 Constructor<?> boxConstructor = boxClass.getConstructor(Object.class);
16 System.out.println(Arrays.toString(boxConstructor.getParameterTypes()));
17 } catch (NoSuchMethodException e) {
18 e.printStackTrace();
19 }
20 }
21}
22
23/*
24 * output:
25 * [class java.lang.Object]
26 */
重要
泛型的限制:
- 不能实例化泛型类型:不能使用
new T()
这样的语法 - 不能创建泛型数组:不能声明
T[] array = new T[10]
- 静态成员不能使用类的泛型类型参数:静态方法和字段属于类,而不是实例
- 不能使用基本数据类型作为泛型类型参数:必须使用包装类(如
Integer
而不是int
) - 不能捕获泛型类型的异常:
catch
块不能使用泛型类型参数