第42章 Java泛型
面包屑导航 章节标题 学习目标
学习目标
- 理解泛型的概念和优势
- 掌握泛型类的定义和使用
- 学会编写和使用泛型方法
- 理解泛型通配符的使用场景
- 掌握有界类型参数的应用
- 了解类型擦除机制
- 学会处理泛型数组和反射
- 掌握PECS原则和泛型最佳实践
泛型概述
什么是泛型
泛型(Generics)是Java 5引入的一个重要特性,它提供了编译时类型安全检测机制,允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型的主要优势类型安全:在编译时检查类型错误,避免运行时ClassCastException消除类型转换:避免显式的类型转换,代码更简洁提高代码重用性:一套代码适用于多种类型提高性能:避免装箱和拆箱操作更好的API设计:使API更加清晰和易用
// 没有泛型的时代(Java 5之前)
List list = new ArrayList();
list.add("Hello");
list.add(123); // 可以添加任意类型
String str = (String) list.get(0); // 需要强制类型转换
// 使用泛型(Java 5之后)
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误,类型不匹配
String str = list.get(0); // 无需类型转换
泛型类型
泛型类型
泛型类
- 在类名后使用
声明类型参数 - 类型参数可以在类的任何地方使用
- 支持多个类型参数
- 可以有有界类型参数
public class Container<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
泛型方法
- 在方法返回类型前声明
- 可以在静态方法中使用
- 独立于类的泛型参数
- 支持有界类型参数
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
泛型接口
- 接口可以声明类型参数
- 实现类可以指定具体类型
- 实现类也可以保持泛型
- 支持多重继承的泛型接口
public interface Comparable<T> {
int compareTo(T other);
}
public class Person implements Comparable<Person> {
public int compareTo(Person other) {
// 比较逻辑
}
}
泛型通配符
泛型通配符
通配符是泛型中的一个重要概念,用问号(?)表示未知类型。通配符主要有三种形式:
| 通配符类型 | 语法 | 说明 | 使用场景 |
|---|---|---|---|
| 上界通配符 | ? extends T | 只能读取,不能写入 | 生产者(Producer) |
| 下界通配符 | ? super T | 只能写入,不能读取 | 消费者(Consumer) |
| 无界通配符 | ? | 只能进行与类型无关的操作 | 不确定类型时 |
PECS原则
Producer Extends, Consumer Super - 这是使用通配符的黄金法则:
- 如果你需要从集合中读取数据(生产者),使用? extends T
- 如果你需要向集合中写入数据(消费者),使用? super T
- 如果既要读取又要写入,不要使用通配符
// 上界通配符 - 只能读取
public static double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number num : numbers) {
total += num.doubleValue(); // 只能读取
}
return total;
}
// 下界通配符 - 只能写入
public static void addNumbers(List<? super Integer> numbers) {
numbers.add(1); // 只能写入Integer或其子类
numbers.add(2);
numbers.add(3);
}
// 无界通配符 - 类型无关操作
public static int getSize(List<?> list) {
return list.size(); // 只能进行与类型无关的操作
}
有界类型参数
有界类型参数
有界类型参数允许你限制类型参数的范围,使用extends关键字来指定类型参数必须是某个类的子类或实现某个接口。
// 限制T必须是Number或其子类
public class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) {
this.number = number;
}
// 可以调用Number类的方法
public double getDoubleValue() {
return number.doubleValue();
}
}
// T必须同时继承Number并实现Comparable接口
public class ComparableNumber<T extends Number & Comparable<T>> {
private T value;
public ComparableNumber(T value) {
this.value = value;
}
public boolean isGreaterThan(T other) {
return value.compareTo(other) > 0;
}
public double getDoubleValue() {
return value.doubleValue();
}
}
类型擦除
类型擦除
Java泛型是通过类型擦除来实现的,这意味着泛型信息只存在于编译时,运行时会被擦除。这是Java泛型与C#泛型的主要区别。
类型擦除的影响运行时无法获取泛型的具体类型信息不能创建泛型数组:new T[10]不能实例化类型参数:new T()不能使用基本类型作为类型参数不能在静态上下文中使用类型参数
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// 运行时类型相同,都是ArrayList
System.out.println(stringList.getClass() == integerList.getClass()); // true
// 泛型信息被擦除
System.out.println(stringList.getClass()); // class java.util.ArrayList
System.out.println(integerList.getClass()); // class java.util.ArrayList
完整代码示例
完整代码示例
/**
* 泛型基础示例
* 演示泛型类和泛型方法的基本使用
*/
public class GenericBasics {
/**
* 泛型类示例 - 简单的容器类
*/
public static class Container<T> {
private T item;
public Container(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
public void displayInfo() {
System.out.println("容器中的项目: " + item);
System.out.println("项目类型: " + item.getClass().getSimpleName());
}
}
/**
* 泛型方法示例 - 交换数组中两个元素的位置
*/
public static <T> void swap(T[] array, int i, int j) {
if (array == null || i < 0 || j < 0 || i >= array.length || j >= array.length) {
throw new IllegalArgumentException("无效的参数");
}
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
/**
* 主方法 - 演示泛型的基本使用
*/
public static void main(String[] args) {
System.out.println("=== Java泛型基础示例 ===");
// 1. 泛型类的使用
Container<String> stringContainer = new Container<>("Hello, Generics!");
stringContainer.displayInfo();
Container<Integer> intContainer = new Container<>(42);
intContainer.displayInfo();
// 2. 泛型方法的使用
String[] names = {"Alice", "Bob", "Charlie", "David"};
System.out.println("交换前: " + Arrays.toString(names));
swap(names, 0, 3);
System.out.println("交换后: " + Arrays.toString(names));
}
}
💻 查看完整代码 - 在线IDE体验 泛型最佳实践
泛型最佳实践
泛型使用原则
推荐做法
// 使用泛型提高类型安全
List<String> names = new ArrayList<>();
// 遵循PECS原则
public void processNumbers(List<? extends Number> numbers) {
// 只读取数据
}
public void addIntegers(List<? super Integer> numbers) {
// 只写入数据
}
// 使用有界类型参数
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
- 优先使用泛型而不是原始类型
- 遵循PECS原则使用通配符
- 使用有界类型参数限制类型范围
- 泛型方法优于泛型类
避免的做法
// 使用原始类型
List list = new ArrayList(); // 避免
// 不必要的通配符
List<?> list = new ArrayList<String>(); // 过度使用
// 忽略编译器警告
@SuppressWarnings("unchecked")
List<String> list = (List<String>) rawList; // 危险
- 避免使用原始类型
- 不要忽略编译器的泛型警告
- 避免不必要的通配符使用
- 不要在新代码中混用泛型和原始类型
常见问题
常见问题解答
Q: 什么时候使用泛型?A: 当你需要类型安全、避免类型转换、提高代码重用性时。特别是在集合、工具类、框架开发中。
Q: extends和super的区别?A: extends用于读取(生产者),super用于写入(消费者)。记住PECS原则:Producer Extends, Consumer Super。
Q: 为什么不能创建泛型数组?A: 由于类型擦除,运行时无法确定数组的确切类型,可能导致类型安全问题。可以使用List代替数组,或者使用反射创建。
本章小结
本章小结
- Java泛型提供编译时类型安全,避免运行时类型转换错误
- 泛型包括泛型类、泛型方法和泛型接口三种主要形式
- 通配符分为上界(? extends)、下界(? super)和无界(?)三种
- PECS原则:Producer Extends, Consumer Super
- 有界类型参数使用extends关键字限制类型范围
- 类型擦除机制导致运行时泛型信息丢失
- 泛型不能使用基本类型,不能创建泛型数组
- 优先使用泛型而不是原始类型,遵循最佳实践
导航链接 上一章:Java注解 章节测试 下一章:Java集合框架 JavaScript