集合框架与泛型 - 泛型¶
1. 概述¶
在Java编程中,泛型(Generics)是一种允许类、接口和方法在定义时使用类型参数的机制。泛型的主要目的是提供类型安全,并减少类型转换的需要。通过使用泛型,我们可以编写更通用、更灵活的代码,同时避免运行时类型转换错误。
1.1 为什么需要泛型?¶
在没有泛型的情况下,Java集合类(如ArrayList
)只能存储Object
类型的对象。这意味着我们可以将任何类型的对象放入集合中,但在取出时需要进行强制类型转换。这种设计容易导致运行时错误,因为编译器无法在编译时检查类型是否匹配。
泛型的引入解决了这个问题,它允许我们在编译时指定集合中存储的对象类型,从而避免了运行时类型转换错误。
2. 泛型的基本概念¶
2.1 泛型类¶
泛型类是指具有一个或多个类型参数的类。类型参数在类定义时指定,并在实例化类时确定具体的类型。
// 定义一个泛型类 Box,T 是类型参数
class Box<T> {
private T item;
// 设置 item 的值
public void setItem(T item) {
this.item = item;
}
// 获取 item 的值
public T getItem() {
return item;
}
}
// 使用泛型类
public class Main {
public static void main(String[] args) {
// 创建一个存储 Integer 类型的 Box
Box<Integer> integerBox = new Box<>();
integerBox.setItem(123);
System.out.println("Integer Box: " + integerBox.getItem());
// 创建一个存储 String 类型的 Box
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello, Generics!");
System.out.println("String Box: " + stringBox.getItem());
}
}
解释: - Box<T>
是一个泛型类,T
是类型参数。 - 在实例化 Box
时,我们可以指定 T
的具体类型,如 Integer
或 String
。 - 通过泛型,我们可以在编译时确保类型安全,避免了运行时类型转换错误。
2.2 泛型方法¶
泛型方法是指在方法定义时使用类型参数的方法。泛型方法可以在普通类或泛型类中定义。
// 定义一个泛型方法
public class GenericMethodExample {
// 泛型方法,T 是类型参数
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
// 使用泛型方法打印 Integer 数组
Integer[] intArray = {1, 2, 3, 4, 5};
printArray(intArray);
// 使用泛型方法打印 String 数组
String[] strArray = {"Hello", "World"};
printArray(strArray);
}
}
解释: - printArray
是一个泛型方法,<T>
表示类型参数。 - 在调用 printArray
时,编译器会根据传入的数组类型自动推断 T
的具体类型。 - 泛型方法可以处理不同类型的数组,而不需要为每种类型编写单独的方法。
2.3 泛型接口¶
泛型接口是指具有一个或多个类型参数的接口。实现泛型接口时,可以指定具体的类型参数。
// 定义一个泛型接口
interface Pair<K, V> {
K getKey();
V getValue();
}
// 实现泛型接口
class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 使用泛型接口
public class Main {
public static void main(String[] args) {
// 创建一个存储 String 和 Integer 的 OrderedPair
Pair<String, Integer> pair = new OrderedPair<>("One", 1);
System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
}
}
解释: - Pair<K, V>
是一个泛型接口,K
和 V
是类型参数。 - OrderedPair
类实现了 Pair
接口,并在实例化时指定了具体的类型参数。 - 通过泛型接口,我们可以定义更通用的接口,并在实现时指定具体的类型。
3. 泛型的通配符¶
泛型通配符(Wildcard)用于表示未知类型。通配符通常用于方法参数中,以增加方法的灵活性。
3.1 上界通配符¶
上界通配符(<? extends T>
)表示类型参数是 T
或其子类。
// 定义一个方法,使用上界通配符
public class WildcardExample {
public static void printList(List<? extends Number> list) {
for (Number number : list) {
System.out.print(number + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
// 可以传入 Integer 或 Double 的 List
printList(intList);
printList(doubleList);
}
}
解释: - printList
方法接受一个 List<? extends Number>
参数,表示可以传入 Number
或其子类的 List
。 - 通过上界通配符,我们可以编写更通用的方法,同时保持类型安全。
3.2 下界通配符¶
下界通配符(<? super T>
)表示类型参数是 T
或其父类。
// 定义一个方法,使用下界通配符
public class WildcardExample {
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList);
System.out.println("Number List: " + numberList);
}
}
解释: - addNumbers
方法接受一个 List<? super Integer>
参数,表示可以传入 Integer
或其父类的 List
。 - 通过下界通配符,我们可以向集合中添加特定类型的元素,同时保持类型安全。
4. 练习题¶
4.1 简单练习¶
- 编写一个泛型类
Pair
,它包含两个类型参数T
和U
,并实现getFirst
和getSecond
方法。
4.2 中等练习¶
- 编写一个泛型方法
swap
,它接受一个List<T>
和两个索引,交换这两个索引处的元素。
4.3 复杂练习¶
- 编写一个泛型类
Stack<T>
,实现栈的基本操作(push
、pop
、peek
),并使用泛型确保类型安全。
5. 总结¶
- 泛型允许我们在编译时指定类型参数,从而提高代码的类型安全性和灵活性。
- 泛型类、泛型方法和泛型接口是泛型的主要应用形式。
- 通配符(
<? extends T>
和<? super T>
)用于增加方法的灵活性,同时保持类型安全。 - 通过使用泛型,我们可以编写更通用、更健壮的代码,减少运行时错误。
通过本主题的学习,你应该能够理解泛型的基本概念,并能够在实际编程中应用泛型来编写更安全、更灵活的代码。