作为一名 Java 开发工程师,你一定在使用集合类(如 List、Map)时遇到过类型转换异常或运行时错误。Java 5 引入的 泛型(Generics) 就是为了帮助我们解决这些问题 —— 实现编译期的类型检查和类型安全。
本文将带你全面掌握:
- 泛型的基本概念与作用
- 泛型类、接口、方法的定义与使用
- 类型擦除机制与边界限制
- 通配符(?)、上下界限定(extends/super)
- 自定义泛型类与泛型工具设计
- 泛型常见问题与最佳实践
并通过丰富的代码示例和真实业务场景讲解,帮助你写出更健壮、可维护性更高的 Java 代码。
🧱 一、什么是泛型?
泛型(Generics) 是 Java 中的一种参数化机制,允许我们在定义类、接口或方法时使用“类型参数”,从而实现对多种类型的统一处理。
✅ 核心作用:
| 作用 | 描述 |
|---|---|
| 类型安全 | 在编译期进行类型检查,避免运行时 ClassCastException |
| 避免强制类型转换 | 不再需要频繁地 (T)obj 转换 |
| 提高代码复用性 | 同一套逻辑可以适用于不同数据类型 |
示例对比:
没有泛型:
List list = new ArrayList();
list.add("Hello");
list.add(123); // 编译通过,但运行时报错
String s = (String) list.get(1); // ClassCastException
使用泛型:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add(123); // 编译报错!不能添加 Integer 到 List<String>
🔍 二、泛型的基本使用
1. 泛型类
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
使用方式:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
Box<Integer> intBox = new Box<>();
intBox.set(123);
2. 泛型接口
public interface Repository<T, ID> {
T findById(ID id);
void save(T entity);
}
实现方式:
public class UserRepository implements Repository<User, Long> {
@Override
public User findById(Long id) {
// ...
}
@Override
public void save(User user) {
// ...
}
}
3. 泛型方法
public class ArrayUtils {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
使用方式:
String[] names = {"Tom", "Jerry"};
ArrayUtils.printArray(names);
Integer[] numbers = {1, 2, 3};
ArrayUtils.printArray(numbers);
🧠 三、类型擦除(Type Erasure)
Java 的泛型是 伪泛型(编译期特性) ,JVM 并不真正支持泛型,它在编译阶段会进行 类型擦除(Type Erasure) ,即把所有泛型信息替换为原始类型。
示例说明:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
💡 所有泛型在运行时都被擦除,变为
Object或者指定的上界类型。
⚖️ 四、泛型的边界限制(Bounded Type Parameters)
我们可以使用 extends 来限制泛型的类型范围,确保其具备某些功能。
示例:
public static <T extends Number> double sum(List<T> numbers) {
return numbers.stream().mapToDouble(Number::doubleValue).sum();
}
使用方式:
List<Integer> ints = Arrays.asList(1, 2, 3);
double total = sum(ints); // 正确
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0);
total = sum(doubles); // 正确
List<String> strings = Arrays.asList("a", "b");
// sum(strings); // 编译错误,String 不是 Number 子类
🔁 五、通配符(Wildcard)与上下界限定
1. 无界通配符:<?>
表示未知类型,适合只读操作。
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
使用方式:
List<String> strList = Arrays.asList("A", "B");
printList(strList);
List<Integer> intList = Arrays.asList(1, 2);
printList(intList);
2. 上界通配符:<? extends T>
只能读取,不能写入(除了 null),适合消费者模式。
public static double getTotalArea(List<? extends Shape> shapes) {
return shapes.stream().mapToDouble(Shape::getArea).sum();
}
3. 下界通配符:<? super T>
可以写入 T 类型的对象,适合生产者模式。
public static void addShapes(List<? super Rectangle> list) {
list.add(new Rectangle());
}
🧪 六、PECS 原则(Producer Extends, Consumer Super)
这是使用通配符的一个重要原则:
| 场景 | 通配符选择 |
|---|---|
| 只读(生产者) | <? extends T> |
| 只写(消费者) | <? super T> |
| 既读又写 | 不使用通配符 |
🧱 七、自定义泛型类设计实战
场景:通用结果封装类(用于返回 API 响应)
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.code = 200;
result.message = "成功";
result.data = data;
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.code = code;
result.message = message;
return result;
}
// Getter / Setter 省略
}
使用方式:
Result<User> userResult = Result.success(new User("Tom"));
Result<List<Product>> productResult = Result.error(500, "服务器内部错误");
🚫 八、泛型的局限性与注意事项
| 局限性 | 说明 |
|---|---|
| 不能使用基本类型 | 必须使用包装类(如 Integer 而非 int) |
| 不能创建泛型数组 | new T[10] 会编译错误 |
| 不能实例化泛型类型 | new T() 不合法 |
| 类型擦除导致无法重载 | void method(List<String>) 和 void method(List<Integer>) 冲突 |
| 不能捕获泛型异常 | catch (T e) 不合法 |
| 不能使用 instanceof 判断泛型类型 | if (obj instanceof List<String>) 编译错误 |
📊 九、总结:Java 泛型核心知识点一览表
| 内容 | 说明 |
|---|---|
| 泛型类 | class Box<T> |
| 泛型接口 | interface Repository<T, ID> |
| 泛型方法 | <T> void print(T t) |
| 通配符 | <?>, <? extends T>, <? super T> |
| 类型擦除 | JVM 不识别泛型,编译时替换为 Object |
| PECS 原则 | Producer Extends, Consumer Super |
| 边界限制 | T extends Comparable<T> |
| 自定义泛型类 | 如通用响应结构体、工具类等 |
| 注意事项 | 不能使用基本类型、不能 new T() 等 |
📎 十、附录:泛型常用技巧速查表
| 技巧 | 示例 |
|---|---|
| 定义泛型类 | public class Pair<K, V> |
| 定义泛型方法 | public static <T> List<T> asList(T... elements) |
| 上界限定 | T extends Number |
| 下界限定 | T super Integer |
| 通配符只读 | List<? extends Animal> |
| 通配符只写 | List<? super Dog> |
| 返回泛型对象 | return (T) someObject; |
| 获取泛型实际类型 | 使用反射(需保留泛型信息) |
| 多边界泛型 | <T extends Serializable & Cloneable> |
| 泛型数组模拟 | 使用 ArrayList<T> 替代 |
如果你正在准备一篇面向初学者的技术博客,或者希望系统回顾 Java 基础知识,这篇文章将为你提供完整的知识体系和实用的编程技巧。
欢迎点赞、收藏、转发,也欢迎留言交流你在实际项目中遇到的泛型相关问题。我们下期再见 👋
📌 关注我,获取更多Java核心技术深度解析!