Java 泛型
泛型(Generics)让"类型"也能被参数化:写类或方法时先用占位符,使用时再指定具体类型。它把类型错误提前到编译期发现,省去大量强转,集合框架是最大的受益者。
泛型解决什么问题
没有泛型时,集合里装的是 Object,取出必须强转,转错了要到运行期才暴露:
// 不用泛型:取出要强转,容易运行期出错
List list = new ArrayList();
list.add("hello");
String s1 = (String) list.get(0); // 每次都强转
// 用泛型:编译期就确定元素类型
List<String> list2 = new ArrayList<>();
list2.add("hello");
String s2 = list2.get(0); // 免强转,类型安全
泛型类 Box<T>
类名后用尖括号声明类型参数,类内部就能用 T 充当任意类型:
class Box<T> { // T 是类型参数
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
// —— 片段需放进 main 方法中运行 ——
Box<String> box = new Box<>();
box.set("苹果");
System.out.println(box.get());
// 输出:苹果
类型参数命名约定:E 元素、K 键、V 值、T 类型、N 数字,见名知意。
泛型方法
方法声明中在返回值前加 <T>,该 T 只对本方法有效,参数与返回值可共用:
public class Demo {
public static <T> void printArr(T[] arr) { // 泛型方法
for (T t : arr) System.out.print(t + " ");
System.out.println();
}
public static void main(String[] args) { // 完整程序
printArr(new Integer[]{1, 2, 3});
printArr(new String[]{"A", "B"});
}
}
// 输出:1 2 3
// 输出:A B
通配符 ? 、? extends 与 ? super
通配符用于"不确定具体类型"的场合:
List<?>:任何类型的 List 都能接收,但只能读、不能往里写。List<? extends Number>:上界通配符,可接收 Number 及其子类(Integer、Double…),适合读。List<? super Integer>:下界通配符,可接收 Integer 及其父类(Number、Object…),适合写。
public static void show(List<? extends Number> list) {
for (Number n : list) System.out.println(n); // 读出的是 Number
}
泛型集合的典型用法如 List<String>、Map<String, Integer>、Set<Double>,声明后就带上类型约束。
类型擦除与不可协变
- 类型擦除:编译后泛型信息会被抹掉,
List<String>变成原始类型 List,运行期拿不到 T 的真实类型。 - 不可协变:
List<String>不是List<Object>的子类型,直接传参会编译报错,这是为了类型安全;需要"多类型兼容"时用? extends通配符。
泛型把类型检查从运行期提前到编译期:泛型类/方法定义模板,通配符负责灵活兼容,类型擦除提醒我们运行期没有 T——用好它,代码既安全又少写强转。