开篇概览:泛型的核心价值
泛型(Generics) 是 Java 5(JDK 1.5)引入的重要特性,其核心目标是:
- 编译期类型安全检查:避免运行时
ClassCastException; - 消除强制类型转换:代码更简洁、可读;
- 提升代码复用性:一套逻辑适配多种数据类型。
泛型的本质是参数化类型(Parameterized Types)——将类型作为参数传递给类、接口或方法,从而在编译阶段确保类型一致性。
本章将深入讲解:
- 泛型基本语法与原理(类型擦除);
- 泛型类、泛型方法、泛型接口;
- 类型通配符(
?、<? extends T>、<? super T>); - 边界限定与最佳实践。
一、泛型基本原理:类型擦除(Type Erasure)
1.1 什么是类型擦除?
- Java 的泛型是编译期特性,运行时不存在泛型信息;
- 编译器在编译时擦除泛型类型参数,替换为上限类型(通常是
Object); - 通过桥接方法(Bridge Methods) 和强制类型转换保证类型安全。
示例:泛型编译后等价代码
// 源码:泛型类
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
// 编译后(等价于)
public class Box {
private Object value;
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
✅ 优势:兼容 Java 5 之前的字节码;
❌ 局限:运行时无法获取泛型实际类型(如T.class非法)。
二、泛型类(Generic Class)
2.1 定义与使用
- 在类名后使用
<T>声明类型参数; T是类型变量(可为任意标识符,常用T,E,K,V)。
示例:通用容器类
// 中文注释:定义一个泛型盒子类,可存储任意类型的数据
public class Box<T> {
// 私有字段,类型为泛型 T
private T content;
// 设置内容(参数类型为 T)
public void setContent(T content) {
this.content = content;
}
// 获取内容(返回类型为 T)
public T getContent() {
return content;
}
// 重写 toString 方法,便于打印
@Override
public String toString() {
return "Box 内容: " + content;
}
}
// 测试泛型类
public class GenericClassDemo {
public static void main(String[] args) {
// 创建存储字符串的盒子
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello 泛型");
System.out.println(stringBox); // Box 内容: Hello 泛型
// 创建存储整数的盒子
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
System.out.println(intBox); // Box 内容: 123
// 编译器自动类型检查:以下代码会报错
// stringBox.setContent(456); // ❌ 编译错误:Integer 不能赋值给 String
}
}
2.2 多类型参数
// 中文注释:定义一个键值对泛型类(类似简化版 Map)
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
@Override
public String toString() {
return "Pair{" + key + "=" + value + "}";
}
}
// 使用
Pair<String, Integer> score = new Pair<>("张三", 95);
System.out.println(score); // Pair{张三=95}
三、泛型方法(Generic Method)
3.1 定义与使用
- 在返回类型前声明类型参数
<T>; - 可独立于类的泛型存在(普通类也可定义泛型方法)。
示例:泛型工具方法
// 中文注释:定义一个包含泛型方法的工具类
public class GenericMethodDemo {
// 泛型方法:交换数组中两个位置的元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 泛型方法:打印任意类型数组
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}
public static void main(String[] args) {
// 字符串数组
String[] names = {"张三", "李四", "王五"};
swap(names, 0, 2);
printArray(names); // 王五 李四 张三
// 整数数组
Integer[] numbers = {1, 2, 3};
swap(numbers, 0, 1);
printArray(numbers); // 2 1 3
}
}
✅ 优势:方法逻辑与具体类型解耦,复用性强。
四、泛型接口(Generic Interface)
4.1 定义与实现
- 接口可声明泛型参数;
- 实现类可指定具体类型,或保留泛型。
示例:泛型数据处理器
// 中文注释:定义一个泛型数据处理接口
interface DataProcessor<T> {
// 处理数据并返回结果
T process(T data);
}
// 实现类:指定具体类型(String)
class StringProcessor implements DataProcessor<String> {
@Override
public String process(String data) {
return data.toUpperCase(); // 转为大写
}
}
// 实现类:保留泛型(通用处理器)
class IdentityProcessor<T> implements DataProcessor<T> {
@Override
public T process(T data) {
return data; // 原样返回
}
}
// 测试
public class GenericInterfaceDemo {
public static void main(String[] args) {
DataProcessor<String> stringProc = new StringProcessor();
System.out.println(stringProc.process("hello")); // HELLO
DataProcessor<Integer> intProc = new IdentityProcessor<>();
System.out.println(intProc.process(123)); // 123
}
}
五、类型通配符(Wildcards)
通配符 ? 用于表示未知类型,增强泛型的灵活性。
5.1 无界通配符 <?>
- 表示“任意类型”;
- 只能读取,不能写入(除
null外)。
示例:安全遍历任意类型列表
// 中文注释:打印任意类型 List 的内容(只读)
public static void printList(List<?> list) {
for (Object item : list) { // 通配符列表只能转为 Object
System.out.println(item);
}
// list.add("new"); // ❌ 编译错误:无法确定列表实际类型
}
public class WildcardDemo {
public static void main(String[] args) {
List<String> strList = Arrays.asList("A", "B");
List<Integer> intList = Arrays.asList(1, 2);
printList(strList); // 正常输出
printList(intList); // 正常输出
}
}
5.2 上界通配符 <? extends T>
- 表示“T 或 T 的子类型”;
- PECS 原则:Producer-Extends(生产者用
extends); - 可读不可写(因编译器不知具体子类型)。
示例:计算数字列表的总和
// 中文注释:计算 Number 及其子类(Integer, Double 等)列表的总和
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) { // 安全:所有元素都是 Number
sum += num.doubleValue();
}
return sum;
}
public class UpperBoundedWildcardDemo {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
System.out.println("整数和: " + sumOfList(integers)); // 6.0
System.out.println("小数和: " + sumOfList(doubles)); // 7.5
// sumOfList(Arrays.asList("A", "B")); // ❌ 编译错误:String 不是 Number 子类
}
}
5.3 下界通配符 <? super T>
- 表示“T 或 T 的父类型”;
- PECS 原则:Consumer-Super(消费者用
super); - 可写不可读(读取时只能转为
Object)。
示例:将字符串列表复制到任意父类型列表
// 中文注释:将源列表(String)复制到目标列表(String 或其父类,如 Object)
public static void copy(List<String> src, List<? super String> dest) {
for (String s : src) {
dest.add(s); // 安全:dest 至少能接受 String
}
}
public class LowerBoundedWildcardDemo {
public static void main(String[] args) {
List<String> src = Arrays.asList("Java", "泛型");
List<Object> dest = new ArrayList<>();
copy(src, dest); // 成功:Object 是 String 的父类
System.out.println(dest); // [Java, 泛型]
// copy(src, new ArrayList<Integer>()); // ❌ 编译错误:Integer 不是 String 父类
}
}
六、泛型边界限定(Bounded Type Parameters)
在定义泛型时限制类型参数的范围。
6.1 上界限定 <T extends 类/接口>
T必须是指定类型或其子类;- 可指定多个接口(用
&连接),但只能有一个类。
示例:限定泛型为可比较类型
// 中文注释:定义一个泛型方法,要求类型实现 Comparable 接口
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// 多重边界:必须是 Number 且实现 Comparable
public static <T extends Number & Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
public class BoundedTypeDemo {
public static void main(String[] args) {
System.out.println(max("Apple", "Banana")); // Banana
System.out.println(min(3.14, 2.71)); // 2.71
// max(123, "abc"); // ❌ 编译错误:类型不一致
}
}
七、泛型最佳实践与注意事项
✅ 推荐做法
- 优先使用泛型:避免原始类型(如
List而非List<String>); - 遵循 PECS 原则:
- 生产数据(读取) →
<? extends T> - 消费数据(写入) →
<? super T>
- 生产数据(读取) →
- 方法参数尽量用通配符,返回类型避免通配符;
- 泛型类中避免创建泛型数组(可用
ArrayList替代)。
❌ 避免做法
- 泛型与基本类型混用:需使用包装类(
List<int>非法,应为List<Integer>); - 运行时依赖泛型类型:
// ❌ 非法:无法获取 T 的 Class T instance = new T(); Class<T> clazz = T.class; - 在静态上下文中使用类泛型参数:
public class Box<T> { // ❌ 非法:静态方法不能访问类泛型 T public static void method(T t) { } }
八、总结:泛型核心要点
| 特性 | 说明 |
|---|---|
| 类型擦除 | 运行时无泛型信息,编译期检查 |
| 泛型类 | class Box<T> { ... } |
| 泛型方法 | <T> void method(T t) |
| 泛型接口 | interface Processor<T> { ... } |
| 无界通配符 | List<?> — 任意类型,只读 |
| 上界通配符 | List<? extends Number> — 生产者 |
| 下界通配符 | List<? super String> — 消费者 |
| 边界限定 | <T extends Comparable<T>> |
📌 核心思想:
“泛型是编译器的语法糖,但却是类型安全的守护者。”
合理使用泛型,可让代码更安全、简洁、可维护,是现代 Java 开发的必备技能。
538

被折叠的 条评论
为什么被折叠?



