Java 泛型
泛型(Generics)是 Java 5 引入的重要特性,它提供了编译时类型安全检测机制,允许程序员在编译时检测到非法的类型。
为什么需要泛型?
没有泛型时的问题
// Java 5 之前的写法
List list = new ArrayList();
list.add("Hello");
list.add(123); // 可以添加任意类型
// 取出时需要强制类型转换
String s = (String) list.get(0); // OK
String s2 = (String) list.get(1); // 运行时 ClassCastException!
使用泛型的好处
// 使用泛型后
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误!类型安全
String s = list.get(0); // 不需要强制转换
泛型的优点:
- 类型安全:编译时检查类型,避免运行时错误
- 消除强制类型转换:代码更简洁
- 提高代码复用性:可以编写通用的算法和数据结构
泛型类
定义泛型类
// T 是类型参数(Type Parameter)
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");
String str = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.set(123);
Integer num = intBox.get();
// Java 7+ 可以使用菱形语法
Box<Double> doubleBox = new Box<>(); // 右边不需要指定类型
多个类型参数
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; }
}
// 使用
Pair<String, Integer> pair = new Pair<>("年龄", 25);
System.out.println(pair.getKey() + ": " + pair.getValue());
常见的类型参数命名
按照惯例,类型参数使用单个大写字母:
| 参数 | 含义 |
|---|---|
T | Type(类型) |
E | Element(元素,常用于集合) |
K | Key(键) |
V | Value(值) |
N | Number(数值) |
R | Result(结果) |
S, U, V | 第二、三、四个类型 |
泛型接口
定义泛型接口
public interface Generator<T> {
T generate();
}
// 实现方式1:指定具体类型
public class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "Hello";
}
}
// 实现方式2:保留类型参数
public class GenericGenerator<T> implements Generator<T> {
@Override
public T generate() {
return null; // 实际实现需要返回 T 类型
}
}
泛型接口示例
// 比较器接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
// 实现
public class StringLengthComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
泛型方法
泛型方法是在方法声明中定义类型参数的方法,类型参数的作用域仅限于该方法。
定义泛型方法
public class Utils {
// 泛型方法:<T> 声明类型参数
public static <T> T getFirst(List<T> list) {
if (list.isEmpty()) {
return null;
}
return list.get(0);
}
// 多个类型参数
public static <K, V> void print(K key, V value) {
System.out.println(key + " = " + value);
}
// 带边界的泛型方法
public static <T extends Number> double sum(List<T> list) {
double total = 0;
for (T num : list) {
total += num.doubleValue();
}
return total;
}
}
// 使用泛型方法(类型推断)
List<String> names = Arrays.asList("A", "B", "C");
String first = Utils.getFirst(names);
// 也可以显式指定类型(通常不需要)
String first2 = Utils.<String>getFirst(names);
Utils.print("年龄", 25);
泛型方法 vs 泛型类
public class Demo<T> {
// 这是泛型类的方法,使用类的类型参数
public T method1(T value) {
return value;
}
// 这是泛型方法,有自己的类型参数(与类的 T 无关)
public static <E> E method2(E value) {
return value;
}
// 警告:类型参数隐藏了类的类型参数
public <T> void method3(T value) { // 这里的 T 不是类的 T
// ...
}
}
类型通配符
通配符用于表示未知的类型,用 ? 表示。
无界通配符 ?
// 可以接受任何类型
public void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
List<String> strings = Arrays.asList("A", "B", "C");
List<Integer> integers = Arrays.asList(1, 2, 3);
printList(strings); // OK
printList(integers); // OK
// 注意:不能向无界通配符的集合添加元素(除了 null)
List<?> list = new ArrayList<String>();
// list.add("Hello"); // 编译错误
list.add(null); // 只有 null 可以
上界通配符 ? extends T
表示类型必须是 T 或 T 的子类,用于安全地读取数据。
// 可以读取 Number 或其子类的列表
public double sum(List<? extends Number> list) {
double total = 0;
for (Number num : list) {
total += num.doubleValue(); // 可以安全地读取为 Number
}
return total;
}
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0);
System.out.println(sum(integers)); // 6.0
System.out.println(sum(doubles)); // 6.0
// 注意:不能添加元素(编译器无法确定具体类型)
List<? extends Number> numbers = new ArrayList<Integer>();
// numbers.add(10); // 编译错误
下界通配符 ? super T
表示类型必须是 T 或 T 的父类,用于安全地写入数据。
// 可以向列表添加 Integer 或其子类
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(numbers); // OK
addNumbers(objects); // OK
// 注意:读取时只能得到 Object
List<? super Integer> list = new ArrayList<Number>();
list.add(10);
Object obj = list.get(0); // 只能作为 Object 读取
PECS 原则
Producer Extends, Consumer Super
- 生产者(Producer):如果你只需要从集合中读取数据,使用
? extends T - 消费者(Consumer):如果你只需要向集合中写入数据,使用
? super T
// 生产者:从 src 读取数据
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i)); // 从 src 读取,写入 dest
}
}
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Number> numbers = new ArrayList<>(Arrays.asList(0, 0, 0));
copy(numbers, integers); // Number super Integer, Integer extends Integer
类型擦除
Java 泛型是通过类型擦除(Type Erasure)实现的,编译器在编译时会移除所有泛型类型信息。
类型擦除过程
// 编译前
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; // T 被擦除为 Object
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
有边界的类型擦除
// 编译前
public class NumberBox<T extends Number> {
private T value;
public T get() { return value; }
}
// 编译后
public class NumberBox {
private Number value; // T 被擦除为边界类型 Number
public Number get() { return value; }
}
类型擦除的影响
// 1. 不能用基本类型作为类型参数
// List<int> list; // 错误!
List<Integer> list; // 正确
// 2. 运行时类型检查只能检查原始类型
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// 两者在运行时类型相同
System.out.println(strings.getClass() == integers.getClass()); // true
// 3. 不能创建泛型数组
// T[] array = new T[10]; // 错误!
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10]; // 通过,但有警告
// 4. 不能实例化类型参数
// public class Box<T> {
// T value = new T(); // 错误!
// }
泛型的限制
1. 不能使用基本类型
// 错误
// Pair<int, double> pair;
// 正确:使用包装类
Pair<Integer, Double> pair;
2. 运行时类型查询
List<String> strings = new ArrayList<>();
// 错误:不能使用泛型类型进行 instanceof 检查
// if (strings instanceof List<String>) { }
// 正确:使用原始类型
if (strings instanceof List) { }
// 获取原始类型
Class<?> clazz = strings.getClass(); // ArrayList.class
3. 不能创建泛型数组
// 错误
// List<String>[] array = new List<String>[10];
// 正确:使用 List 或 Object[]
List<List<String>> listOfLists = new ArrayList<>();
Object[] array = new List[10];
4. 不能实例化类型参数
public class Factory<T> {
// 错误
// T create() { return new T(); }
// 正确:传入 Class 对象或 Supplier
public T create(Class<T> clazz) throws Exception {
return clazz.getDeclaredConstructor().newInstance();
}
public T create(java.util.function.Supplier<T> supplier) {
return supplier.get();
}
}
// 使用
Factory<String> factory = new Factory<>();
String s = factory.create(String.class);
String s2 = factory.create(() -> "Hello");
5. 静态成员不能使用类型参数
public class Box<T> {
// 错误:静态变量不能使用类的类型参数
// private static T defaultValue;
// 错误:静态方法不能使用类的类型参数
// public static T getDefault() { return defaultValue; }
// 正确:静态方法可以定义自己的类型参数
public static <E> E process(E value) {
return value;
}
}
泛型的实际应用
1. 通用容器类
public class Container<T> {
private T value;
public Container(T value) {
this.value = value;
}
public T get() { return value; }
public void set(T value) { this.value = value; }
public boolean isPresent() {
return value != null;
}
public <R> R map(Function<T, R> mapper) {
return isPresent() ? mapper.apply(value) : null;
}
public void ifPresent(Consumer<T> consumer) {
if (isPresent()) {
consumer.accept(value);
}
}
}
2. 泛型工具方法
public class CollectionUtils {
// 转换列表
public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
// 过滤列表
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// 查找元素
public static <T> T findFirst(List<T> list, Predicate<T> predicate) {
for (T item : list) {
if (predicate.test(item)) {
return item;
}
}
return null;
}
}
// 使用
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> lengths = CollectionUtils.map(names, String::length);
List<String> longNames = CollectionUtils.filter(names, s -> s.length() > 3);
3. 类型安全的异构容器
public class TypeSafeContainer {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
map.put(type, instance);
}
public <T> T get(Class<T> type) {
return type.cast(map.get(type));
}
}
// 使用:可以存储不同类型的对象
TypeSafeContainer container = new TypeSafeContainer();
container.put(String.class, "Hello");
container.put(Integer.class, 123);
container.put(List.class, Arrays.asList(1, 2, 3));
String s = container.get(String.class); // "Hello"
Integer i = container.get(Integer.class); // 123
List<?> list = container.get(List.class); // [1, 2, 3]
4. Builder 模式
public class HttpRequest<T> {
private String url;
private String method;
private Map<String, String> headers;
private T body;
private HttpRequest(Builder<T> builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.body = builder.body;
}
public static class Builder<T> {
private String url;
private String method = "GET";
private Map<String, String> headers = new HashMap<>();
private T body;
public Builder<T> url(String url) {
this.url = url;
return this;
}
public Builder<T> method(String method) {
this.method = method;
return this;
}
public Builder<T> header(String key, String value) {
headers.put(key, value);
return this;
}
public Builder<T> body(T body) {
this.body = body;
return this;
}
public HttpRequest<T> build() {
return new HttpRequest<>(this);
}
}
}
// 使用
HttpRequest<User> request = new HttpRequest.Builder<User>()
.url("/api/users")
.method("POST")
.header("Content-Type", "application/json")
.body(new User("张三", 25))
.build();
小结
本章我们学习了:
- 泛型基础:为什么需要泛型、泛型的优点
- 泛型类:定义和使用泛型类
- 泛型接口:定义和实现泛型接口
- 泛型方法:定义和使用泛型方法
- 类型通配符:无界通配符、上界通配符、下界通配符
- PECS 原则:生产者使用 extends,消费者使用 super
- 类型擦除:理解 Java 泛型的实现机制
- 泛型的限制:基本类型、类型查询、数组、实例化等限制
- 实际应用:容器类、工具方法、异构容器、Builder 模式
练习
- 创建一个泛型栈类
Stack<T>,支持 push、pop、peek 操作 - 创建一个泛型方法,交换数组中两个元素的位置
- 使用泛型实现一个简单的缓存类
- 实现一个泛型方法,找出数组中的最大值和最小值
- 使用 PECS 原则实现一个通用的集合复制方法