跳到主要内容

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); // 不需要强制转换

泛型的优点:

  1. 类型安全:编译时检查类型,避免运行时错误
  2. 消除强制类型转换:代码更简洁
  3. 提高代码复用性:可以编写通用的算法和数据结构

泛型类

定义泛型类

// 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());

常见的类型参数命名

按照惯例,类型参数使用单个大写字母:

参数含义
TType(类型)
EElement(元素,常用于集合)
KKey(键)
VValue(值)
NNumber(数值)
RResult(结果)
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();

小结

本章我们学习了:

  1. 泛型基础:为什么需要泛型、泛型的优点
  2. 泛型类:定义和使用泛型类
  3. 泛型接口:定义和实现泛型接口
  4. 泛型方法:定义和使用泛型方法
  5. 类型通配符:无界通配符、上界通配符、下界通配符
  6. PECS 原则:生产者使用 extends,消费者使用 super
  7. 类型擦除:理解 Java 泛型的实现机制
  8. 泛型的限制:基本类型、类型查询、数组、实例化等限制
  9. 实际应用:容器类、工具方法、异构容器、Builder 模式

练习

  1. 创建一个泛型栈类 Stack<T>,支持 push、pop、peek 操作
  2. 创建一个泛型方法,交换数组中两个元素的位置
  3. 使用泛型实现一个简单的缓存类
  4. 实现一个泛型方法,找出数组中的最大值和最小值
  5. 使用 PECS 原则实现一个通用的集合复制方法