本文目录导读:

- 案例一:让一个类处理多种类型(类型安全与复用)
- 案例二:泛型方法——优雅地实现灵活工具类
- 案例三:泛型接口与通配符——实现生产者与消费者的解耦
- 案例四:类型安全地构建树形结构(递归泛型)
- 案例五:多泛型参数与类型擦除的巧用
- 泛型的四个核心价值
泛型是Java中非常重要的特性,它通过类型参数化,让代码更安全、更灵活、更易复用,下面通过几个经典案例,展示泛型的“妙用”所在。
这些案例从简单到复杂,覆盖了泛型最核心的应用场景:类型安全、代码复用、解耦与抽象。
让一个类处理多种类型(类型安全与复用)
这是泛型最基础的用法,没有泛型时,我们用Object定义容器,每次使用时必须手动强制转换,容易在运行时抛出ClassCastException。
无泛型的问题:
public class Box {
private Object item;
public void set(Object item) { this.item = item; }
public Object get() { return item; }
}
// 使用
Box box = new Box();
box.set("Hello");
Integer integer = (Integer) box.get(); // 编译通过,运行报错 ClassCastException
有泛型的妙用:
public class Box<T> {
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
// Integer i = stringBox.get(); // 编译错误,类型安全
String str = stringBox.get(); // 不需要强制类型转换
妙处: 编译器在编译期就帮你检查了类型错误,运行时安全干净,代码自文档化。
泛型方法——优雅地实现灵活工具类
泛型不仅可以用于类,还可以用于方法,这个方法并不依赖整个类的泛型,只对方法本身进行参数化。
场景: 创建一个工具方法,安全地将数组转换为List。
public class GenericUtils {
// 这是一个泛型方法,<T> 声明在返回值之前
public static <T> List<T> arrayToList(T[] array) {
List<T> list = new ArrayList<>();
for (T element : array) {
list.add(element);
}
return list;
}
}
// 使用
String[] strArray = {"A", "B", "C"};
List<String> strList = GenericUtils.arrayToList(strArray); // 自动推断 T 为 String,安全
Integer[] intArray = {1, 2, 3};
List<Integer> intList = GenericUtils.arrayToList(intArray); // 自动推断 T 为 Integer
妙处: 同一个方法可以为任意类型的数组工作,完全类型安全,如果没有泛型方法,你需要为每种类型写一个重载方法。
泛型接口与通配符——实现生产者与消费者的解耦
这是Java集合框架中最精妙的设计之一。Collection、List、Map大量使用了泛型和通配符,让API具有极高的灵活性。
场景: 一个统计任意类型列表元素个数的通用方法,并展示? extends T和? super T的妙用。
// 生产者(Producer):只读数据,使用 ? extends T
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// 消费者(Consumer):只写数据,使用 ? super T
public static void addIntegers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i); // 可以安全添加 Integer
}
}
// 使用
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5);
List<Number> numbers = new ArrayList<>();
System.out.println(sumOfList(ints)); // 可以传入 List<Integer>
System.out.println(sumOfList(doubles)); // 可以传入 List<Double>
addIntegers(numbers); // 可以传入 List<Number>
addIntegers(ints); // 也可以传入 List<Integer>(但 List<Integer> 作为消费者也可以)
妙处: List<? extends Number> 表示 “这个列表里的元素都是Number或其子类”,让你安全地读取(生产者)。List<? super Integer> 表示 “这个列表能容纳Integer或其父类”,让你安全地写入(消费者),这就是著名的PECS原则(Producer Extends, Consumer Super)。
类型安全地构建树形结构(递归泛型)
泛型可以用来定义递归结构,让树、链表等数据结构更安全。
场景: 构建一个二叉树的节点,要求左孩子和右孩子与父节点是同一种类型。
// 递归类型边界
public class TreeNode<T extends Comparable<T>> {
private T value;
private TreeNode<T> left;
private TreeNode<T> right;
public TreeNode(T value) {
this.value = value;
}
public void insert(T newValue) {
if (newValue.compareTo(this.value) < 0) {
if (left == null) {
left = new TreeNode<>(newValue);
} else {
left.insert(newValue);
}
} else if (newValue.compareTo(this.value) > 0) {
if (right == null) {
right = new TreeNode<>(newValue);
} else {
right.insert(newValue);
}
}
}
}
妙处: T extends Comparable<T> 这个边界声明强制要求泛型类型必须实现Comparable接口,从而可以安全地在节点间进行值的比较,如果一个类没有实现Comparable,编译就直接报错,不会等到运行时。
多泛型参数与类型擦除的巧用
场景: 创建一个键值对对象,并让某个方法返回不同类型的值。
// 多泛型参数
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; }
}
// 利用类型擦除,实现无泛型的桥梁方法
public class MyList<T> {
private Object[] elements = new Object[10];
private int size = 0;
public void add(T item) {
elements[size++] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) {
return (T) elements[index]; // 这里利用了类型擦除后的强制转换
}
}
妙处:
Pair<K, V>让一个对象能安全地承载两个不同类型的值,且类型信息保留。- 虽然泛型在运行时被擦除(
T变为Object),但通过桥接模式和强制转换,编译器依然保证了外部调用的类型安全。
泛型的四个核心价值
| 场景 | 核心妙用 |
|---|---|
| 类型安全 | 消除强制类型转换,编译期检查,避免ClassCastException |
| 代码复用 | 一套代码处理多种类型,无需重复编写重载方法 |
| 抽象与解耦 | 通配符(? extends/? super)实现生产者/消费者的灵活组合 |
| 自文档化 | 泛型参数名(如T、K、V)让代码意图更清晰(如Map<String, Integer>说明键是字符串,值是整数) |
一句话总结: 泛型的妙用在于它 “在不牺牲类型安全的前提下,实现了代码的最大化复用和抽象化”,任何需要处理多种类型、需要容器、需要约束的场景,都是泛型大放异彩的地方。