Java 泛型深度精讲:从泛型类到通配符,一篇文章彻底掌握
✔️ 类型安全 · ✔️ 消除强制转换 · ✔️ PECS 原则 —— 让你的代码更通用、更安全
目录
📝 代码(GenericInterfaceDemo.java)
📌 适用人群:Java 初学者、准备面试、编写通用库的开发者
🔥 你将学到:泛型类、泛型方法、泛型接口、通配符 ?、上下界、类型擦除原理
📚 一、知识点速览
| 知识点 | 说明 | 重要度 |
|---|---|---|
| 泛型类 | 类名后加 <T>,定义类型参数 |
⭐⭐⭐⭐⭐ |
| 泛型方法 | 返回值前加 <T>,独立于类的类型参数 |
⭐⭐⭐⭐⭐ |
| 泛型接口 | 接口名后加 <K,V>,实现类指定或保留类型参数 |
⭐⭐⭐⭐ |
通配符 ? |
表示未知类型,配合 extends / super |
⭐⭐⭐⭐⭐ |
| PECS 原则 | Producer Extends, Consumer Super | ⭐⭐⭐⭐ |
| 类型擦除 | 编译后泛型信息被移除,替换为 Object 或边界 |
⭐⭐⭐⭐ |
🧪 二、实验环境准备
-
JDK 8+
-
VS Code / IntelliJ IDEA
-
新建项目
GenericBasicDemo,创建三个 Java 文件(分别对应三个场景)
✨ 三、场景一:泛型类 —— 类型安全的容器
🎯 目标
定义一个泛型类 Box<T>,可以存放任意类型的数据,取出时无需强制转换。
📝 代码(GenericBoxDemo.java)
java
/**
* 场景一:泛型类的基本使用
* 演示类型安全,避免强制转换
*/
public class GenericBoxDemo {
// 泛型类 Box<T>,T 是类型参数
static class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
public static void main(String[] args) {
System.out.println("=== 场景一:泛型类 ===");
// Box 专门装 String
Box<String> stringBox = new Box<>();
stringBox.set("Hello Generic");
String str = stringBox.get(); // 无需强制转换
System.out.println("String box contains: " + str);
// Box 专门装 Integer
Box<Integer> intBox = new Box<>();
intBox.set(100);
int num = intBox.get(); // 自动拆箱
System.out.println("Integer box contains: " + num);
}
}
✅ 运行结果
text
=== 场景一:泛型类 ===
String box contains: Hello Generic
Integer box contains: 100
📸 截图1:GenericBoxDemo.java 完整代码及运行结果截图

🧠 核心知识点
-
Box<T>中的T称为类型参数,使用时替换为具体类型(如String、Integer)。 -
编译器会在编译时检查类型一致性,避免将
Integer放入Box<String>。 -
取出元素时自动是具体类型,无需强制转换。
🔄 四、场景二:泛型方法 —— 通用的数组元素交换
🎯 目标
编写一个泛型方法 swap,可以交换任意类型数组中两个元素的位置。
📝 代码(GenericMethodDemo.java)
java
import java.util.Arrays;
/**
* 场景二:泛型方法
* 交换数组中两个元素的位置
*/
public class GenericMethodDemo {
// 泛型方法 <T> 放在返回值之前
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
System.out.println("=== 场景二:泛型方法 ===");
// 交换 String 数组
String[] words = {"A", "B", "C"};
System.out.println("交换前: " + Arrays.toString(words));
swap(words, 0, 2);
System.out.println("交换后: " + Arrays.toString(words));
// 交换 Integer 数组
Integer[] numbers = {1, 2, 3};
System.out.println("\n交换前: " + Arrays.toString(numbers));
swap(numbers, 0, 2);
System.out.println("交换后: " + Arrays.toString(numbers));
}
}
✅ 运行结果
text
=== 场景二:泛型方法 ===
交换前: [A, B, C]
交换后: [C, B, A]
交换前: [1, 2, 3]
交换后: [3, 2, 1]
📸 截图3:GenericMethodDemo.java 代码及运行结果截图

🧠 核心知识点
-
泛型方法的类型参数
<T>放在返回值之前。 -
该方法可以独立于类的类型参数,甚至可以定义在普通类中。
-
编译器通过类型推断自动确定
T的具体类型(如String、Integer)。
🔗 五、场景三:泛型接口 + 通配符 —— 键值对容器
🎯 目标
定义一个泛型接口 Pair<K,V>,实现类 OrderedPair,并使用通配符 ? 打印任意类型的 Pair 列表。
📝 代码(GenericInterfaceDemo.java)
java
import java.util.ArrayList;
import java.util.List;
/**
* 场景三:泛型接口 + 通配符
* 定义一个泛型接口,并实现
*/
public class GenericInterfaceDemo {
// 泛型接口 Pair<K, V>
interface Pair<K, V> {
K getKey();
V getValue();
}
// 实现类,保留类型参数
static class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 通配符示例:打印任意类型的 Pair 列表
// 注意:这里的写法需要修正,应为 List<? extends Pair<?, ?>>
public static void printPairs(List<? extends Pair<?, ?>> pairs) {
for (Pair<?, ?> p : pairs) {
System.out.println("Key: " + p.getKey() + ", Value: " + p.getValue());
}
}
public static void main(String[] args) {
System.out.println("=== 场景三:泛型接口与通配符 ===");
List<Pair<String, Integer>> list = new ArrayList<>();
list.add(new OrderedPair<>("Age", 25));
list.add(new OrderedPair<>("Score", 98));
list.add(new OrderedPair<>("Rank", 1));
printPairs(list);
}
}
✅ 运行结果
text
=== 场景三:泛型接口与通配符 ===
Key: Age, Value: 25
Key: Score, Value: 98
Key: Rank, Value: 1
📸 截图5:GenericInterfaceDemo.java 代码及运行结果截图

🧠 核心知识点
-
泛型接口定义了两个类型参数
<K, V>。 -
通配符
?表示未知类型。List<? extends Pair<?, ?>>表示列表中的元素是Pair的某种子类型,且Pair的键值类型也未知。 -
PECS 原则:这里
List<? extends ...>用于生产(读取)数据,符合extends作为上界。
🔬 六、进阶拔高:类型擦除与通配符原理
1. 类型擦除
泛型信息只在编译期存在,运行时会被擦除。上述 Box<String> 编译后实际上变成了 Box,所有 T 被替换为 Object。
2. 通配符的设计意图
-
? extends T:上界通配符,允许读取为T,但不能写入(除了null)。适用于生产者。 -
? super T:下界通配符,允许写入T及其子类型,但读取时只能得到Object。适用于消费者。
3. 无限定通配符 ?
Pair<?, ?> 表示键和值的类型都未知,只能读取并赋值给 Object。
⚠️ 七、常见陷阱与解决方案
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 不能创建泛型数组 | new T[10] 编译错误 |
使用 ArrayList<T> 代替 |
| 静态域不能引用类型参数 | static T instance 非法 |
将静态域移到非泛型类 |
| 基本类型不能作为类型参数 | Box<int> 报错 |
使用包装类 Box<Integer> |
| 运行时无法区分泛型类型 | list instanceof List<String> 非法 |
使用通配符 list instanceof List<?> |
📊 八、泛型 vs 非泛型 —— 终极对比
| 特性 | 非泛型(Object) |
泛型 |
|---|---|---|
| 类型安全 | ❌ 运行时可能 ClassCastException |
✅ 编译期检查 |
| 代码可读性 | 强制转换遍布 | 清晰,无需转换 |
| 灵活性 | 极高(可存任意类型) | 有限但有界 |
| 性能 | 无开销 | 相同(擦除后) |
💡 九、课后思考题
-
为什么不能创建泛型数组?有什么替代方案?
-
什么是 PECS 原则?举例说明
? extends和? super的使用场景。 -
如何编写一个泛型方法,实现将任意类型的
List复制到另一个List中?
📚 十、参考文献
-
Oracle. The Java™ Tutorials: Generics [Online].
-
Joshua Bloch. Effective Java (3rd Edition) Item 26–33.
-
耿祥义, 张跃平. Java面向对象程序设计(第4版) 第8章.
🙏 写在最后
如果你觉得这篇文章对你有帮助,请点赞👍 + 收藏⭐ + 评论💬 支持一下!
你的鼓励是我持续输出硬核技术文章的动力。
更多推荐
所有评论(0)