第17课:泛型
本课目标:理解泛型的概念和作用,掌握泛型类、泛型方法、泛型接口的使用,了解通配符和类型擦除。
一、概念讲解
1.1 为什么需要泛型
没有泛型的问题:
// 需要为每种类型写重复代码
public class IntBox {
private int value;
public void set(int value) { this.value = value; }
public int get() { return value; }
}
public class StringBox {
private String value;
public void set(String value) { this.value = value; }
public String get() { return value; }
}
使用泛型:
// 一个类适用于所有类型
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
1.2 泛型的优势
泛型的优势:
1. 类型安全:编译时检查类型
Box<Integer> box = new Box<>();
box.set(10); // OK
box.set("hello"); // 编译错误!
2. 消除强制类型转换
// 无泛型:Object obj = list.get(0); String s = (String) obj;
// 有泛型:String s = list.get(0);
3. 代码复用:一份代码适用多种类型
二、语法格式
2.1 泛型类
// 单个类型参数
public class Box<T> {
private T content;
public void set(T content) { this.content = content; }
public T get() { return content; }
}
// 多个类型参数
public class Pair<K, V> {
private K key;
private V value;
// 构造器、getter、setter
}
// 使用
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();
Pair<String, Integer> pair = new Pair<>("age", 25);
2.2 泛型方法
// 类型参数放在返回值前面
public class Utils {
public static <T> T getFirst(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
// 多个类型参数
public static <K, V> Map<K, V> zip(K[] keys, V[] values) {
Map<K, V> map = new HashMap<>();
for (int i = 0; i < keys.length && i < values.length; i++) {
map.put(keys[i], values[i]);
}
return map;
}
}
// 使用(类型推断)
String first = Utils.getFirst(new String[]{"a", "b"});
Integer num = Utils.getFirst(new Integer[]{1, 2, 3});
2.3 泛型接口
// 定义泛型接口
public interface Repository<T> {
void save(T entity);
T findById(int id);
List<T> findAll();
}
// 实现时指定具体类型
public class UserRepository implements Repository<User> {
@Override
public void save(User entity) { /* ... */ }
@Override
public User findById(int id) { /* ... */ }
@Override
public List<User> findAll() { /* ... */ }
}
// 实现时保留泛型
public class GenericRepository<T> implements Repository<T> {
@Override
public void save(T entity) { /* ... */ }
@Override
public T findById(int id) { /* ... */ }
@Override
public List<T> findAll() { /* ... */ }
}
2.4 通配符
// 无界通配符:接受任何类型
public void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
// 上界通配符:接受Number及其子类
public double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) {
total += n.doubleValue();
}
return total;
}
// 下界通配符:接受Integer及其父类
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
三、代码案例
3.1 泛型Box容器
public class Box<T> {
private T content;
private boolean empty = true;
public Box() {
}
public Box(T content) {
this.content = content;
this.empty = false;
}
public void set(T content) {
this.content = content;
this.empty = false;
}
public T get() {
if (empty) {
throw new IllegalStateException("Box为空");
}
return content;
}
public boolean isEmpty() {
return empty;
}
public void clear() {
this.content = null;
this.empty = true;
}
@Override
public String toString() {
if (empty) {
return "Box{空}";
}
return "Box{" + content + "}";
}
public static void main(String[] args) {
System.out.println("=== 泛型Box演示 ===");
// String Box
Box<String> stringBox = new Box<>("Hello");
System.out.println("String Box: " + stringBox);
System.out.println("内容: " + stringBox.get());
// Integer Box
Box<Integer> intBox = new Box<>(100);
System.out.println("Integer Box: " + intBox);
System.out.println("内容: " + intBox.get());
// 空Box
Box<Double> doubleBox = new Box<>();
System.out.println("Double Box: " + doubleBox);
System.out.println("是否为空: " + doubleBox.isEmpty());
}
}
▶ 运行结果:
=== 泛型Box演示 ===
String Box: Box{Hello}
内容: Hello
Integer Box: Box{100}
内容: 100
Double Box: Box{空}
是否为空: true
3.2 泛型Stack
import java.util.EmptyStackException;
public class GenericStack<E> {
private Object[] elements;
private int size;
private static final int DEFAULT_CAPACITY = 10;
@SuppressWarnings("unchecked")
public GenericStack() {
elements = new Object[DEFAULT_CAPACITY];
size = 0;
}
public void push(E element) {
ensureCapacity();
elements[size++] = element;
}
@SuppressWarnings("unchecked")
public E pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
E element = (E) elements[--size];
elements[size] = null;
return element;
}
@SuppressWarnings("unchecked")
public E peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return (E) elements[size - 1];
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
private void ensureCapacity() {
if (size == elements.length) {
Object[] newArray = new Object[elements.length * 2];
System.arraycopy(elements, 0, newArray, 0, size);
elements = newArray;
}
}
public static void main(String[] args) {
System.out.println("=== 泛型Stack演示 ===");
GenericStack<String> stack = new GenericStack<>();
stack.push("Java");
stack.push("Python");
stack.push("C++");
System.out.println("栈大小: " + stack.size());
System.out.println("栈顶: " + stack.peek());
System.out.println("弹出: " + stack.pop());
System.out.println("弹出: " + stack.pop());
System.out.println("栈大小: " + stack.size());
}
}
▶ 运行结果:
=== 泛型Stack演示 ===
栈大小: 3
栈顶: C++
弹出: C++
弹出: Python
栈大小: 1
3.3 泛型Pair和工具类
import java.util.*;
public class GenericUtils {
// 泛型Pair
public static class Pair<A, B> {
private final A first;
private final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
public A getFirst() { return first; }
public B getSecond() { return second; }
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
// 泛型方法:交换数组元素
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 a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
// 泛型方法:过滤列表
public static <T> List<T> filter(List<T> list, java.util.function.Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// 泛型方法:创建不可变Pair
public static <A, B> Pair<A, B> of(A first, B second) {
return new Pair<>(first, second);
}
public static void main(String[] args) {
System.out.println("=== 泛型工具类演示 ===");
// Pair使用
Pair<String, Integer> pair = new Pair<>("年龄", 25);
System.out.println("Pair: " + pair);
System.out.println("Key: " + pair.getFirst());
System.out.println("Value: " + pair.getSecond());
// 交换数组
Integer[] nums = {1, 2, 3, 4, 5};
swap(nums, 0, 4);
System.out.println("交换后: " + Arrays.toString(nums));
// 比较最大值
System.out.println("max(10, 20) = " + max(10, 20));
System.out.println("max(\"Java\", \"Python\") = " + max("Java", "Python"));
// 过滤列表
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = filter(numbers, n -> n % 2 == 0);
System.out.println("偶数: " + evens);
// 静态工厂方法
Pair<String, Boolean> result = of("成功", true);
System.out.println("结果: " + result);
}
}
▶ 运行结果:
=== 泛型工具类演示 ===
Pair: (年龄, 25)
Key: 年龄
Value: 25
交换后: [5, 2, 3, 4, 1]
max(10, 20) = 20
max("Java", "Python") = Python
偶数: [2, 4, 6, 8, 10]
结果: (成功, true)
四、常见错误
4.1 原始类型(Raw Types)
import java.util.*;
public class RawTypeError {
public static void main(String[] args) {
// 原始类型:没有类型参数
List list = new ArrayList(); // 原始类型,编译器会警告
list.add("Hello");
list.add(123); // 可以添加任何类型
// 错误:需要强制类型转换,可能ClassCastException
String s = (String) list.get(1); // 运行时异常!
// 正确:使用泛型
List<String> safeList = new ArrayList<>();
safeList.add("Hello");
// safeList.add(123); // 编译错误,类型安全
}
}
▶ 运行结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
4.2 泛型数组问题
import java.util.*;
public class GenericArrayError {
public static void main(String[] args) {
// 错误:不能创建泛型数组
// T[] array = new T[10]; // 编译错误
// 正确方式1:使用Object数组
@SuppressWarnings("unchecked")
T[] array1 = (T[]) new Object[10];
// 正确方式2:使用ArrayList代替数组
List<T> list = new ArrayList<>();
// 注意:数组有协变性,泛型没有
Object[] objects = new String[10];
objects[0] = "Hello"; // OK
// objects[1] = 123; // ArrayStoreException
// List<String> strings = new ArrayList<Object>(); // 编译错误
}
static class T {}
}
4.3 通配符误用
import java.util.*;
public class WildcardError {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
// 上界通配符:可以读取,不能写入
List<? extends Number> upperBound = integers;
Number n = upperBound.get(0); // OK
// upperBound.add(1); // 编译错误!不能写入
// 下界通配符:可以写入,读取只能是Object
List<? super Integer> lowerBound = new ArrayList<Number>();
lowerBound.add(1); // OK
lowerBound.add(2); // OK
Object obj = lowerBound.get(0); // 只能读取为Object
// PECS原则:Producer Extends, Consumer Super
// 如果集合是生产者(提供数据),用 extends
// 如果集合是消费者(接收数据),用 super
}
}
五、课后练习
-
泛型容器:实现一个泛型的Pair类,支持两个不同类型的操作。
-
泛型排序:编写泛型方法,对任意Comparable类型的数组进行排序。
-
泛型Map包装:实现一个泛型的MapWrapper类,提供put、get、remove等方法。
-
通配符练习:编写方法,接受
List<? extends Number>,返回其中的最大值。 -
类型擦除实验:编写程序,验证泛型在运行时被擦除(通过反射获取泛型信息)。
六、本课小结
| 概念 | 要点 |
|---|---|
| 泛型类 | class Box<T>,类型参数在类名后 |
| 泛型方法 | <T> T method(),类型参数在返回值前 |
| 泛型接口 | interface Repo<T>,实现时指定或保留泛型 |
| 无界通配符 | <?>,接受任何类型 |
| 上界通配符 | <? extends T>,接受T及其子类,只读 |
| 下界通配符 | <? super T>,接受T及其父类,可写 |
| 类型擦除 | 运行时泛型信息被擦除,变成Object或上界类型 |
关键规则:
- 泛型不能用于基本类型(int用Integer代替)
- 不能创建泛型数组(用ArrayList代替)
- PECS原则:Producer Extends, Consumer Super
- 运行时泛型信息被擦除,不能用instanceof检查泛型类型
本课程持续更新中,欢迎关注!

1411

被折叠的 条评论
为什么被折叠?



