java 泛型
本文最后更新于 2025-10-29,文章内容可能已经过时。
一、泛型概述
Java 泛型是 Java 5 引入的重要特性,它提供了编译时类型安全检查机制,并消除了强制类型转换的需要。泛型的本质是"参数化类型",即在编写代码时将类型作为参数传递,而不是在运行时才确定类型。
二、常见泛型符号含义与用途
| 符号 | 含义 | 常见使用场景 | 示例 |
|---|---|---|---|
| T | Type(类型) | 表示任意具体类型,常用于类、接口、方法级别的泛型定义 | public class Box { ... } |
| E | Element(元素) | 多用于集合类中,表示集合中的元素类型 | public interface List { ... } |
| K | Key(键) | 通常用于 Map 中的键类型 | public interface Map<K, V> { ... } |
| V | Value(值) | 通常用于 Map 中的值类型 | public interface Map<K, V> { ... } |
| ? | Unknown(未知) | 表示不确定的类型,用于通配符 | List<?> list = new ArrayList(); |
| S, U, V | 第二、第三、第四个类型参数 | 当一个泛型需要多个类型参数时使用 | public interface BiFunction<T, U, R> { ... } |
重要提示:Java 泛型中并没有强制规定必须使用哪些字母作为类型参数,但为了代码的可读性和一致性,社区中形成了这些约定俗成的命名习惯。
三、泛型的基本使用场景
1. 泛型类
使用场景:当你需要创建一个通用的数据结构,例如列表、栈、队列、字典等,这些结构可以处理各种类型的数据时。
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用示例
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String str = stringBox.getItem(); // 自动类型推断,无需强制转换
Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
Integer num = integerBox.getItem();
2. 泛型接口
使用场景:当你需要设计一组具有通用操作的接口时,例如比较器、转换器、工厂等。
public interface Comparator<T> {
int compare(T o1, T o2);
}
public class StringComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
// 使用示例
Comparator<String> comparator = new StringComparator();
int result = comparator.compare("apple", "banana");
3. 泛型方法
使用场景:当你需要设计一个通用的方法,它可以处理不同类型的参数时。
public class Util {
// 泛型方法定义
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 泛型方法使用
public static <T> T getFirstElement(List<T> list) {
return list.get(0);
}
}
// 使用示例
Integer[] intArray = {1, 2, 3};
Util.printArray(intArray);
String[] strArray = {"a", "b", "c"};
Util.printArray(strArray);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String first = Util.getFirstElement(names);
4. 泛型集合框架
使用场景:Java 集合框架是泛型最广泛应用的场景。
// 不使用泛型(存在类型转换问题)
List list = new ArrayList();
list.add("hello");
list.add(123); // 可以添加任何类型
String s = (String) list.get(0); // 运行时可能抛出 ClassCastException
// 使用泛型(类型安全)
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译时会报错
String s = list.get(0); // 自动类型推断,无需强制转换
5. 有界类型参数
使用场景:限制泛型类型为特定类或其子类。
// 上界:T 必须是 Number 或其子类
public class NaturalNumber<T extends Number> {
private T n;
public NaturalNumber(T n) {
this.n = n;
}
public boolean isEven() {
return n.intValue() % 2 == 0;
}
}
// 使用示例
NaturalNumber<Integer> i = new NaturalNumber<>(6);
NaturalNumber<Double> d = new NaturalNumber<>(6.0);
// NaturalNumber<String> s = new NaturalNumber<>("6"); // 编译错误
四、泛型通配符的高级用法
1. 无界通配符 ?
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 使用示例
List<String> strList = Arrays.asList("a", "b", "c");
printList(strList); // 可以接受任何类型的 List
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(intList); // 也可以接受
2. 上界通配符 <? extends T>
用途:表示某种类型及其子类,适合"只读"操作。
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
// 使用示例
List<Integer> intList = Arrays.asList(1, 2, 3);
double sumInt = sumOfList(intList); // 可以接受 Integer 列表
List<Double> doubleList = Arrays.asList(1.0, 2.0, 3.0);
double sumDouble = sumOfList(doubleList); // 也可以接受 Double 列表
3. 下界通配符 <? super T>
用途:表示某种类型及其父类,适合"写入"操作。
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
// 使用示例
List<Integer> intList = new ArrayList<>();
addNumbers(intList); // 可以接受 Integer 列表
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // 也可以接受 Number 列表
List<Object> objectList = new ArrayList<>();
addNumbers(objectList); // 也可以接受 Object 列表,因为 Object 是 Integer 的父类
五、PECS 原则
生产者-消费者原则 (Producer-Extends, Consumer-Super)
- 如果一个泛型结构是生产者(提供数据),使用
<? extends T>(上界通配符) - 如果一个泛型结构是消费者(消费数据),使用
<? super T>(下界通配符)
// 生产者:提供数据,使用 extends
public static void printList(List<? extends Number> list) {
for (Number number : list) {
System.out.println(number);
}
}
// 消费者:消费数据,使用 super
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
六、泛型的注意事项
-
类型擦除:Java 泛型在编译时会进行类型擦除,运行时无法获取泛型的具体类型信息。
// 编译错误示例 public class GenericArray<T> { private T[] array = new T[10]; // 编译错误 } -
不能创建泛型数组:不能使用
new T[]创建泛型数组。// 编译错误 List<T>[] listArray = new List<T>[10]; -
不能使用 instanceof 检查泛型类型:运行时无法检查泛型类型。
// 编译错误 if (list instanceof List<String>) { ... } -
静态方法/变量不能使用类的泛型类型参数:
public class MyClass<T> { // 编译错误 public static T staticField; // 编译错误 public static void staticMethod(T t) { } }
七、实际应用案例
1. 泛型在集合框架中的应用
// 使用泛型确保类型安全
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // 编译错误
// 自动类型推断,无需强制转换
String first = names.get(0);
2. 泛型在 Map 中的应用
// 使用 K 和 V 表示键值对
Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 30);
ageMap.put("Bob", 25);
// 获取值,类型安全
Integer aliceAge = ageMap.get("Alice");
3. 泛型在工具类中的应用
public class CollectionUtil {
// 泛型方法:获取集合中的第一个元素
public static <T> T getFirstElement(List<T> list) {
return list.get(0);
}
// 泛型方法:交换数组元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 使用示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String first = CollectionUtil.getFirstElement(names);
Integer[] numbers = {1, 2, 3};
CollectionUtil.swap(numbers, 0, 2);
八、总结
- T:最通用的类型参数,适用于大多数泛型场景
- E:集合框架中表示元素类型
- K/V:Map 中表示键和值的类型
- ?:通配符,表示未知类型,配合上界和下界使用
- S/U/V:多个泛型参数时的扩展使用
泛型提供了类型安全、代码复用和可读性的优势,是 Java 语言中非常重要的特性。在实际开发中,合理使用泛型可以大大提高代码的质量和可维护性。
重点备注:泛型是编译时的特性,运行时会被擦除。理解类型擦除机制有助于避免常见的泛型使用错误。
- 感谢你赐予我前进的力量

