泛型
Java 5 引入的泛型(Generics)让类、接口、方法可以参数化类型。它是集合类型安全的基石,也是阅读主流框架代码的必修课。
1. 为什么需要泛型
没有泛型时,我们用 Object 装任何类型,存取都要强转——又丑又容易 ClassCastException。
// 没有泛型的世界
List list = new ArrayList();
list.add("hello");
Integer n = (Integer) list.get(0); // 运行时 ClassCastException泛型把类型错误前移到编译期。
2. 泛型类
类名后用 <T> 声明一个或多个类型参数。T 是惯例字母(T / K / V / E),你可以用任何标识符。
public class Main {
static class Box<T> {
private T value;
void put(T v) { this.value = v; }
T get() { return value; }
boolean isEmpty() { return value == null; }
}
public static void main(String[] args) {
Box<String> sb = new Box<>();
sb.put("hi");
// sb.put(123); // 编译错误:类型不匹配
System.out.println("String box: " + sb.get());
Box<Integer> ib = new Box<>();
ib.put(42);
System.out.println("Integer box: " + ib.get());
Box<Double> db = new Box<>();
System.out.println("Empty? " + db.isEmpty());
}
}3. 泛型方法
方法自己声明类型参数,放在返回类型之前。
import java.util.*;
import java.util.stream.*;
public class Main {
// 泛型方法:把数组转成 List
static <T> List<T> fromArray(T[] arr) {
return Arrays.stream(arr).collect(Collectors.toList());
}
// 两个参数的类型可以不同
static <K, V> Map<K, V> pair(K k, V v) {
Map<K, V> m = new HashMap<>();
m.put(k, v);
return m;
}
public static void main(String[] args) {
String[] langs = {"Java", "Go", "Rust"};
List<Integer> lens = fromArray(langs).stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("lens = " + lens);
Map<String, Integer> m = pair("age", 18);
System.out.println("pair = " + m);
}
}4. 受限类型参数 Bounded
用 extends 限制类型参数必须是某类的子类。
public class Main {
// 必须是 Number 的子类(Integer, Double, Long...)
static <T extends Number> double sum(List<T> nums) {
double s = 0;
for (T n : nums) s += n.doubleValue();
return s;
}
// 多重限制:必须是 A 且 B
static <T extends Number & Comparable<T>> T maxOf(List<T> list) {
T best = list.get(0);
for (T t : list) if (t.compareTo(best) > 0) best = t;
return best;
}
public static void main(String[] args) {
System.out.println("sum = " + sum(List.of(1, 2.5, 3L))); // 6.5
System.out.println("max = " + maxOf(List.of(3, 1, 4, 1, 5, 9, 2, 6)));
}
}5. 通配符 ?
通配符表示「未知类型」,常用于读取或写入场景:
?任意类型? extends T上界——T 或子类(适合只读)? super T下界——T 或父类(适合只写)
import java.util.*;
public class Main {
// 只读:传入任何 Number 子类的 List
static double total(List<? extends Number> list) {
double s = 0;
for (Number n : list) s += n.doubleValue();
return s;
}
// 只写:把 Integer 放进任何 Integer 父类的 List
static void addInts(List<? super Integer> sink) {
sink.add(1);
sink.add(2);
sink.add(3);
}
public static void main(String[] args) {
List<Integer> ints = List.of(10, 20, 30);
List<Double> dbls = List.of(1.5, 2.5);
System.out.println("ints total = " + total(ints));
System.out.println("dbls total = " + total(dbls));
List<Number> nums = new ArrayList<>();
addInts(nums);
System.out.println("after addInts = " + nums);
}
}💡PECS 原则
Producer Extends, Consumer Super:
- 数据从集合「流出」用
? extends T(生产者) - 数据向集合「流入」用
? super T(消费者)
Collections.copy(dest, src) 就是经典案例:dest 用 super,src 用 extends。
6. 类型擦除(Type Erasure)
泛型只在编译期存在。编译后,Box<String> 和 Box<Integer> 都会被擦成 Box,类型参数替换为 Object(或上界)。这是 Java 选择「伪泛型」的代价。
// 编译前
Box<String> b1 = new Box<>();
Box<Integer> b2 = new Box<>();
b1.put("hi");
// 编译后(字节码层面)
Box b1 = new Box();
Box b2 = new Box();
b1.put((String) "hi");⚠️擦除带来的限制
- 不能
new T():T运行时不存在 - 不能
instanceof List<String>:运行时只剩List - 不能创建参数化类型数组:
new List<String>[10]❌,但new List<?>[10]✅ - 静态字段不能在泛型类里使用类型参数
7. 实战:泛型栈
import java.util.*;
public class Main {
static class Stack<T> {
private final ArrayDeque<T> data = new ArrayDeque<>();
void push(T v) { data.push(v); }
T pop() { return data.pop(); }
T peek() { return data.peek(); }
boolean isEmpty() { return data.isEmpty(); }
int size() { return data.size(); }
}
public static void main(String[] args) {
Stack<Integer> s = new Stack<>();
for (int i = 1; i <= 5; i++) s.push(i * 10);
System.out.println("top = " + s.peek());
System.out.println("size = " + s.size());
while (!s.isEmpty()) {
System.out.print(s.pop() + " ");
}
System.out.println();
}
}🎯 练习
// 任务:实现一个不可变泛型 Pair<K,V>:
// - 构造器接收 k, v
// - k() / v() 访问器
// - 静态方法 of(k, v) 工厂
// - 实现 toString 返回 "Pair(k, v)"
// - 静态方法 swap(Pair<K,V>) 返回 Pair<V,K>
// 期望输出:
// Pair(Alice, 30)
// Pair(30, Alice)
public class Main {
record Pair<K, V>(K k, V v) {
// 补全:swap 静态方法
}
public static void main(String[] args) {
Pair<String, Integer> p = Pair.of("Alice", 30);
System.out.println(p);
System.out.println(Pair.swap(p));
}
}小结
- ✅ 泛型把类型检查前移到编译期,避免
ClassCastException - ✅ 泛型类
<T>、泛型方法<T> ...、菱形new ArrayList<>() - ✅
T extends Number限制上界;可多重限制 - ✅ 通配符
?/? extends T/? super T—— PECS 原则 - ✅ 类型擦除:
T在运行时是Object或上界 - ✅ 不能
new T()、不能instanceof List<String>
下一章 注解:给代码贴「元数据标签」。