泛型
9/6/26JavaJava基础About 3 min
为什么需要泛型
泛型(Generics)是 JDK 5 引入的特性,本质是参数化类型:把类型当作参数传递,使得类、接口、方法可以操作任意数据类型,同时把运行期的 ClassCastException 提前到编译期发现。
// 没有泛型:需要强制转型,且容易放错类型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 如果存了 Integer 运行时才报错
// 有泛型:编译期检查 + 自动转型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 无需强转,类型安全泛型的使用形式
泛型类
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> box = new Box<>();
box.set("content");泛型接口
public interface Comparable<T> {
int compareTo(T o);
}泛型方法
泛型方法的类型参数声明在返回值之前,作用于该方法本身,与所在类是否泛型无关。
public class GenericMethod {
// <T> 写在返回值前面,表示这是一个泛型方法
public static <T> T getMiddle(T... args) {
return args[args.length / 2];
}
}
// 调用时可不显式指定,编译器自动推断
String mid = GenericMethod.getMiddle("a", "b", "c");类型擦除(Type Erasure)
Java 的泛型是伪泛型,只在源码和编译期存在,在编译成字节码后泛型信息会被擦除,替换为限定类型(没有限定则替换为 Object)。这就是为什么泛型被称为“语法糖”。
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
// 编译后都变成 List,运行时类型相同
System.out.println(a.getClass() == b.getClass()); // true擦除规则:
- 无界泛型
<T>→ 擦除为Object。 - 有界泛型
<T extends Number>→ 擦除为Number(第一个边界)。 - 为了维持多态,编译器会在必要时自动插入桥接方法(bridge method)。
桥接方法
class Node<T> {
public T data;
public void setData(T data) { this.data = data; }
}
class IntNode extends Node<Integer> {
// 编译器自动生成桥接方法,保证多态正确:
// public void setData(Object data) { setData((Integer) data); }
public void setData(Integer data) { this.data = data; }
}通配符
上界通配符 ? extends T
表示“T 或 T 的子类”,只能读取,不能写入(除了 null),因为编译器无法确定具体是哪个子类。
// 可以接收 List<Integer>、List<Double> 等 Number 子类
public static double sum(List<? extends Number> list) {
double s = 0;
for (Number n : list) {
s += n.doubleValue();
}
return s;
}下界通配符 ? super T
表示“T 或 T 的父类”,可以写入 T 及其子类,但读取出来只能当作 Object,因为不知道具体是哪个父类。
// 可以接收 List<Integer>、List<Number>、List<Object>
public static void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}无界通配符 ?
表示任意类型,常用于只关心“容器本身、不关心元素类型”的只读/清空场景,例如 Collections.swap、工具方法。
PECS 原则
Producer Extends, Consumer Super
- 如果泛型对象作为生产者(向外提供数据),用
? extends T。 - 如果泛型对象作为消费者(往里写入数据),用
? super T。
// 生产者:从 src 读数据 → extends
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item); // dest 是消费者 → super
}
}泛型约束与限制
- 不能实例化泛型类型参数:
new T()不合法(类型已被擦除)。 - 不能创建泛型数组:
new T[10]不合法;可用List<T>代替。 - 静态成员不能使用类的泛型参数:
private static T t;错误(静态属于类,泛型属于实例)。 - 基本类型不能作为类型参数:必须用包装类,如
List<int>错误,要写List<Integer>。 - 不能对泛型使用
instanceof:if (obj instanceof List<String>)错误,只能obj instanceof List。 - 泛型类不能
extends Throwable:无法定义泛型异常类(catch 时类型已擦除)。
泛型与重载
由于擦除后签名相同,以下两个方法不能共存(编译错误):
public void print(List<String> list) { }
public void print(List<Integer> list) { } // 擦除后签名冲突