Java 泛型(Generics)
泛型是 Java 5 引入的重要特性,它提供了类型安全的集合和类。理解泛型的使用是编写类型安全代码的关键。本章将详细介绍 Java 中的泛型机制。
泛型类与泛型方法
什么是泛型
泛型(Generics)允许在定义类、接口和方法时使用类型参数,在使用时指定具体类型。
优势:
- 类型安全:编译时检查类型
- 消除强制转换:不需要手动类型转换
- 代码复用:一个类可以处理多种类型
泛型类
定义泛型类:
// 泛型类定义
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem(); // 不需要强制转换
Box<Integer> intBox = new Box<>();
intBox.setItem(100);
Integer number = intBox.getItem(); // 类型安全
多个类型参数
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<>("age", 25);
String key = pair.getKey(); // "age"
Integer value = pair.getValue(); // 25
泛型方法
定义泛型方法:
public class Utils {
// 泛型方法
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 泛型方法返回类型
public static <T> T getFirst(T[] array) {
if (array.length > 0) {
return array[0];
}
return null;
}
// 多个类型参数
public static <T, U> void printPair(T first, U second) {
System.out.println("First: " + first);
System.out.println("Second: " + second);
}
}
// 使用
String[] strings = {"a", "b", "c"};
Utils.printArray(strings);
Integer[] numbers = {1, 2, 3};
Integer first = Utils.getFirst(numbers);
泛型接口
// 泛型接口
public interface Comparable<T> {
int compareTo(T other);
}
// 实现泛型接口
public class Student implements Comparable<Student> {
private String name;
private int score;
@Override
public int compareTo(Student other) {
return Integer.compare(this.score, other.score);
}
}
类型通配符
无界通配符(?)
? 表示未知类型:
// 可以接受任何类型的 List
public void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
// 使用
List<String> stringList = Arrays.asList("a", "b");
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(stringList); // ✅ 可以
printList(intList); // ✅ 可以
上界通配符(? extends)
? extends T 表示 T 或其子类型:
// 可以接受 Number 及其子类型的 List
public void processNumbers(List<? extends Number> numbers) {
for (Number num : numbers) {
System.out.println(num.doubleValue());
}
}
// 使用
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0);
processNumbers(integers); // ✅ 可以(Integer extends Number)
processNumbers(doubles); // ✅ 可以(Double extends Number)
// ❌ 错误:不能添加元素(除了 null)
// numbers.add(10); // 编译错误
特点:
- 可以读取元素
- 不能添加元素(除了 null)
- 用于"生产者"场景
下界通配符(? super)
? super T 表示 T 或其父类型:
// 可以接受 Integer 及其父类型的 List
public void addNumbers(List<? super Integer> numbers) {
numbers.add(10); // ✅ 可以添加 Integer
numbers.add(20);
}
// 使用
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addNumbers(numberList); // ✅ 可以(Number super Integer)
addNumbers(objectList); // ✅ 可以(Object super Integer)
// ❌ 错误:不能读取为具体类型
// Integer num = numbers.get(0); // 编译错误
特点:
- 可以添加元素
- 不能读取为具体类型(只能读取为 Object)
- 用于"消费者"场景
PECS 原则
Producer Extends, Consumer Super:
- Producer(生产者):使用
? extends T - Consumer(消费者):使用
? super T
// 生产者:从集合读取
public void copy(List<? extends Number> source, List<? super Number> dest) {
for (Number num : source) {
dest.add(num); // 从 source 读取,添加到 dest
}
}
泛型擦除与限制
类型擦除
Java 的泛型是编译时特性,运行时会被擦除:
// 编译时
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 运行时:类型信息被擦除
// stringList 和 intList 的类型都是 List(原始类型)
System.out.println(stringList.getClass() == intList.getClass()); // true
擦除后的类型:
List<String>→ListBox<Integer>→BoxPair<String, Integer>→Pair
泛型的限制
1. 不能使用基本类型
// ❌ 错误:不能使用基本类型
// List<int> list = new ArrayList<>();
// ✅ 正确:使用包装类型
List<Integer> list = new ArrayList<>();
2. 不能创建泛型数组
// ❌ 错误:不能创建泛型数组
// List<String>[] array = new List<String>[10];
// ✅ 正确:使用原始类型或通配符
List<String>[] array = (List<String>[]) new List[10];
List<?>[] array2 = new List<?>[10];
3. 不能实例化类型参数
public class Box<T> {
// ❌ 错误:不能实例化类型参数
// private T item = new T();
// ✅ 正确:使用反射(需要 Class 参数)
private T createInstance(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
}
4. 静态成员不能使用类型参数
public class Box<T> {
// ❌ 错误:静态成员不能使用类型参数
// private static T staticField;
// ✅ 正确:静态方法可以使用自己的类型参数
public static <U> void staticMethod(U item) {
// ...
}
}
5. 不能捕获泛型异常
// ❌ 错误:不能捕获泛型异常
// try {
// // ...
// } catch (Exception<T> e) { }
// ✅ 正确:使用通配符
// } catch (Exception<?> e) { }
示例:泛型集合、泛型方法
示例 1:泛型类
public class GenericStack<T> {
private List<T> stack = new ArrayList<>();
public void push(T item) {
stack.add(item);
}
public T pop() {
if (stack.isEmpty()) {
throw new IllegalStateException("栈为空");
}
return stack.remove(stack.size() - 1);
}
public T peek() {
if (stack.isEmpty()) {
throw new IllegalStateException("栈为空");
}
return stack.get(stack.size() - 1);
}
public boolean isEmpty() {
return stack.isEmpty();
}
public int size() {
return stack.size();
}
}
// 使用
GenericStack<String> stringStack = new GenericStack<>();
stringStack.push("first");
stringStack.push("second");
System.out.println(stringStack.pop()); // "second"
GenericStack<Integer> intStack = new GenericStack<>();
intStack.push(1);
intStack.push(2);
System.out.println(intStack.pop()); // 2
示例 2:泛型方法
public class ArrayUtils {
// 交换数组元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 查找最大值
public static <T extends Comparable<T>> T max(T[] array) {
if (array.length == 0) {
throw new IllegalArgumentException("数组为空");
}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
// 查找元素
public static <T> int indexOf(T[] array, T target) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(target)) {
return i;
}
}
return -1;
}
}
// 使用
String[] strings = {"a", "b", "c"};
ArrayUtils.swap(strings, 0, 2);
System.out.println(Arrays.toString(strings)); // ["c", "b", "a"]
Integer[] numbers = {3, 1, 4, 1, 5};
Integer max = ArrayUtils.max(numbers);
System.out.println("最大值:" + max); // 5
示例 3:类型通配符
import java.util.*;
public class WildcardExample {
// 上界通配符:读取
public static double sum(List<? extends Number> numbers) {
double sum = 0.0;
for (Number num : numbers) {
sum += num.doubleValue();
}
return sum;
}
// 下界通配符:写入
public static void addNumbers(List<? super Integer> numbers) {
numbers.add(1);
numbers.add(2);
numbers.add(3);
}
// 无界通配符:只读取
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
public static void main(String[] args) {
// 上界通配符
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0);
System.out.println("整数和:" + sum(integers));
System.out.println("浮点数和:" + sum(doubles));
// 下界通配符
List<Number> numbers = new ArrayList<>();
addNumbers(numbers);
System.out.println("数字列表:" + numbers);
// 无界通配符
printList(integers);
printList(doubles);
}
}
示例 4:泛型接口实现
// 泛型接口
public interface Repository<T> {
void save(T entity);
T findById(Long id);
List<T> findAll();
void delete(Long id);
}
// 实现泛型接口
public class UserRepository implements Repository<User> {
private List<User> users = new ArrayList<>();
@Override
public void save(User user) {
users.add(user);
}
@Override
public User findById(Long id) {
return users.stream()
.filter(u -> u.getId().equals(id))
.findFirst()
.orElse(null);
}
@Override
public List<User> findAll() {
return new ArrayList<>(users);
}
@Override
public void delete(Long id) {
users.removeIf(u -> u.getId().equals(id));
}
}
泛型最佳实践
1. 使用有意义的类型参数名
// ✅ 推荐
public class Box<T> { }
public class Pair<K, V> { }
public class Repository<E> { }
// ❌ 不推荐
public class Box<A> { }
public class Pair<X, Y> { }
2. 优先使用泛型方法
// ✅ 推荐:泛型方法更灵活
public static <T> void swap(List<T> list, int i, int j) { }
// ❌ 不推荐:需要泛型类
// public class Utils<T> {
// public void swap(List<T> list, int i, int j) { }
// }
3. 使用通配符提高灵活性
// ✅ 推荐:使用通配符
public void process(List<? extends Number> numbers) { }
// ❌ 不推荐:限制太 严格
// public void process(List<Number> numbers) { }
4. 避免原始类型
// ❌ 不推荐:使用原始类型
List list = new ArrayList();
// ✅ 推荐:使用泛型
List<String> list = new ArrayList<>();
小结
Java 泛型要点:
- 泛型类:定义时使用类型参数,使用时指定具体类型
- 泛型方法:方法级别的类型参数
- 类型通配符:
?、? extends T、? super T - 类型擦除:运行时类型信息被擦除
- 限制:不能使用基本类型、不能创建泛型数组等
关键要点:
- 泛型提供类型安全
- 使用
? extends T读取(生产者) - 使用
? super T写入(消费者) - 理解类型擦除的影响
- 避免使用原始类型
理解了泛型,你就能编写类型安全的代码。在下一章,我们将学习 Java 的 List 集合。