深入学习Java泛型的概念、语法和应用,掌握泛型类、泛型方法、通配符和类型擦除等核心知识
泛型(Generics)是Java 5引入的一个重要特性,它提供了编译时类型安全检测机制,允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
// 没有泛型的时代(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) |
无界通配符 | ? | 只能进行与类型无关的操作 | 不确定类型时 |
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));
}
}
// 使用泛型提高类型安全
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;
}
// 使用原始类型
List list = new ArrayList(); // 避免
// 不必要的通配符
List<?> list = new ArrayList<String>(); // 过度使用
// 忽略编译器警告
@SuppressWarnings("unchecked")
List<String> list = (List<String>) rawList; // 危险
A: 当你需要类型安全、避免类型转换、提高代码重用性时。特别是在集合、工具类、框架开发中。
A: extends用于读取(生产者),super用于写入(消费者)。记住PECS原则:Producer Extends, Consumer Super。
A: 由于类型擦除,运行时无法确定数组的确切类型,可能导致类型安全问题。可以使用List代替数组,或者使用反射创建。