背景与动机
泛型用于把“类型”变成可参数化的东西,让代码在保持复用的同时获得编译期类型检查。
没有泛型时,集合里取出的值往往需要强制转换;有了泛型,编译器能提前知道集合中应该放什么类型。
核心原理拆解
1. 集合泛型
List<String> names = new ArrayList<>();names.add("Tom");String name = names.get(0);List<String> 表示这个列表里应该保存 String。如果尝试放入其他类型,编译器会报错。
2. 泛型类
class Box<T> { private T value;
void set(T value) { this.value = value; }
T get() { return value; }}T 是类型参数,创建对象时再指定具体类型:
Box<String> box = new Box<>();box.set("hello");3. 泛型方法
static <T> T first(T[] array) { return array[0];}方法级别的 <T> 写在返回值类型前面,表示这个方法自己声明了一个类型参数。
4. 通配符 ?
static void printAll(List<?> list) { for (Object item : list) { System.out.println(item); }}List<?> 表示某种未知元素类型的列表,适合只读遍历。
5. 类型擦除
Java 泛型主要在编译期提供类型检查,运行期很多泛型信息会被擦除。
这意味着泛型不是为每个类型生成一份全新的运行时代码,而是通过编译器检查和必要的类型转换来保证类型安全。

最小可运行代码示例
import java.util.ArrayList;import java.util.List;
public class GenericDemo { public static void main(String[] args) { Box<String> box = new Box<>(); box.set("hello"); System.out.println(box.get());
List<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); printAll(numbers); }
static void printAll(List<?> list) { for (Object item : list) { System.out.println(item); } }}
class Box<T> { private T value;
void set(T value) { this.value = value; }
T get() { return value; }}常见陷阱与错误示例
1. 使用原始类型丢失类型安全
错误示例:
List list = new ArrayList();list.add("java");list.add(123);原始类型绕开了泛型检查,后续取值时容易出现类型转换错误。
2. 误以为 List<Object> 可以接收 List<String>
List<String> strings = new ArrayList<>();// List<Object> objects = strings; // 编译失败List<String> 不是 List<Object> 的子类型。需要表达未知类型时,可以使用 List<?>。
3. 在 List<?> 中随意添加元素
List<?> 的元素类型未知,除了 null 外,通常不能安全添加具体元素。
面试高频问题
1. 泛型解决什么问题
泛型让类型成为参数,提升代码复用,同时把类型错误尽量提前到编译期发现。
2. 什么是类型擦除
Java 泛型主要在编译期生效,运行期很多泛型类型信息会被擦除,以保持与早期 Java 的兼容。
3. List<?> 和 List<Object> 有什么区别
List<?> 表示某种未知类型的列表,适合读取;List<Object> 表示元素类型就是 Object 的列表,不能直接接收 List<String>。
一句话总结
Java 泛型用类型参数提升复用和类型安全,但要记住它主要是编译期机制,运行期存在类型擦除。