将通过一个故事来解释 Java 泛型,并结合代码示例说明各种使用场景。
故事场景:快递包裹系统
假设你在一家快递公司工作,负责处理各种类型的包裹。为了让工作更高效,公司引入了一套 "通用容器" 系统(类似于 Java 泛型)。
1. 最初的问题:类型不安全的容器
一开始,公司使用普通的箱子来装所有物品:
java
typescript
// 普通箱子,可以装任何东西
class Box {
private Object item;
public void put(Object item) {
this.item = item;
}
public Object get() {
return item;
}
}
// 使用箱子
Box box = new Box();
box.put("一本书"); // 放入字符串
String book = (String) box.get(); // 需要强制类型转换
问题:
- 箱子可以装任何类型的物品
- 取出物品时需要手动进行类型转换
- 如果放错类型(如放入 Integer),运行时会报错(ClassCastException)
2. 引入泛型:类型安全的容器
后来,公司发明了 "类型标签" 箱子,可以在创建时指定能装的物品类型:
java
typescript
// 泛型箱子,使用类型参数<T>
class Box<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return item;
}
}
// 使用泛型箱子
Box<String> stringBox = new Box<>();
stringBox.put("一本书"); // 只能放String
String book = stringBox.get(); // 无需强制类型转换
Box<Integer> intBox = new Box<>();
intBox.put(123); // 只能放Integer
Integer number = intBox.get();
优点:
- 在编译时检查类型安全
- 无需手动类型转换
- 代码更清晰,可读性更高
泛型的使用场景
1. 泛型类(如 Box)
java
arduino
// 自定义泛型类
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; }
}
// 使用
Pair<String, Integer> pair = new Pair<>("年龄", 18);
String key = pair.getKey(); // String类型
Integer value = pair.getValue(); // Integer类型
2. 泛型方法
java
ini
// 泛型方法:交换数组中两个元素的位置
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 使用
String[] names = {"Alice", "Bob", "Charlie"};
swap(names, 0, 2); // 交换names[0]和names[2]
3. 泛型接口
java
typescript
// 泛型接口:比较器
interface Comparator<T> {
int compare(T o1, T o2);
}
// 实现泛型接口
class StringLengthComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
4. 上限通配符(? extends T)
假设公司有一个 "电子产品" 分类,所有电子产品都继承自Electronics
类:
java
scala
class Electronics {}
class Phone extends Electronics {}
class Laptop extends Electronics {}
// 只能存放Electronics及其子类的箱子
void printElectronics(Box<? extends Electronics> box) {
Electronics item = box.get(); // 安全,因为一定是Electronics类型
// box.put(new Phone()); // 错误!不能放入任何对象(除了null)
}
用途:读取数据,不写入数据
5. 下限通配符(? super T)
假设公司有一个 "员工" 分类,所有员工都继承自Employee
类:
java
scala
class Employee {}
class Manager extends Employee {}
class CEO extends Manager {}
// 可以存放Manager及其父类的箱子
void addManager(Box<? super Manager> box) {
box.put(new Manager()); // 安全,因为Manager及其子类都可以存入
box.put(new CEO()); // 安全,CEO是Manager的子类
// Employee emp = box.get(); // 错误!返回类型是? super Manager,无法确定具体类型
}
用途:写入数据,不读取数据
6. 无界通配符(?)
java
csharp
// 可以存放任何类型的箱子
void printBox(Box<?> box) {
Object item = box.get(); // 安全,因为所有类型都是Object的子类
System.out.println(item);
// box.put("hello"); // 错误!不能放入任何对象(除了null)
}
7. 泛型擦除
Java 泛型在编译后会被擦除为原始类型:
java
ini
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();
System.out.println(stringBox.getClass() == intBox.getClass()); // true
// 运行时,两者都是Box类型
总结
泛型的核心思想:
-
将类型参数化,在使用时指定具体类型
-
提供编译时类型安全检查
-
避免手动类型转换
-
增强代码复用性
使用场景:
-
泛型类:容器类(如 Box、List)
-
泛型方法:通用工具方法
-
泛型接口:定义通用行为
-
上限通配符:读取数据(Producer Extends)
-
下限通配符:写入数据(Consumer Super)
-
无界通配符:处理任意类型
-
泛型擦除:Java 泛型的实现机制
通过这个故事,你可以把 Java 泛型想象成 "类型安全的容器",就像快递公司的标签系统一样,在编译时就确保类型正确,让代码更加健壮和易读。