Java 泛型是对语言类型系统的扩展,允许类和方法按类型参数化。你可以把类型参数看作占位符,就像方法的形式参数是运行时值的占位符一样。
集合框架最能体现泛型的价值。比如 Map 类原本允许添加任意对象,最常见的是保存特定类型的值(如 String)。但 Map.get() 返回 Object,通常必须强制转换:
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");
编译能通过不代表安全。如果映射里存了非 String 对象,运行时会抛出 ClassCastException。理想情况下,我们希望明确 m 是 String 键到 String 值的映射。这就是泛型的作用——消除强转,增加类型检查层。
泛型带来的好处
引入泛型是 Java 的重大增强,不仅语言层面,类库(如集合)也全面重构。
1. 类型安全 泛型的主要目标是提高类型安全。编译器能验证类型假设,错误在编译期就被捕获,而不是等到运行时变成异常。这大大提升了程序的可靠性。
2. 消除强制类型转换 看下面两个例子对比:
不使用泛型:
List li = new ArrayList();
li.put(new Integer(3));
Integer i = (Integer) li.get(0);
使用泛型:
List<Integer> li = new ArrayList<>();
li.put(new Integer(3));
Integer i = li.get(0);
虽然声明时多写了点代码,但在大型项目中,累积减少的强转能让代码更简洁、可读性更高。
3. 潜在性能收益 早期实现中,编译器会插入隐式强转。随着 JVM 优化,更多类型信息可能带来更好的性能。目前所有工作主要在编译器完成,生成类似无泛型的字节码,但更安全。
泛型实战示例
集合框架是泛型最佳应用场景。JDK 5.0 中 Map 接口定义如下:
public interface Map<K, V> {
void put(K key, V value);
V get(K key);
}
K 和 V 是类型参数占位符。实例化时必须指定具体类型:
Map<String, String> m = new HashMap<>();
m.put("key", "blarg");
String s = m.get("key");
此时不再需要强转,因为编译器知道 get() 返回 String。虽然键盘录入没减少,但类型检查从执行时挪到了编译时,开发效率反而提升。
向后兼容
Java 引入泛型的重要目标是维护兼容性。现有代码无需修改即可在 JDK 5+ 运行,只是无法享受类型安全的好处。
命名规范
类型参数推荐用大写字母:
T:泛型类型E:元素(Element),常用于 List/CollectionK:键(Key),用于 MapV:值(Value),用于 Map
泛型不是协变的
很多初学者误以为泛型像数组一样支持协变。其实不然。List<Object> 不是 List<String> 的父类型。
数组是协变的:
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray; // 合法
但泛型不行:
List<Integer> intList = new ArrayList<>();
List<Number> numberList = intList; // 编译错误
为什么?如果允许赋值,下面的代码就会破坏类型安全:
List<Integer> intList = new ArrayList<>();
List<Number> numberList = intList;
numberList.add(new Float(3.14)); // 非法操作被允许
这样 intList 里就混入了非 Integer 对象。所以编译器禁止这种赋值。
类型通配符
假设有个方法打印列表:
void printList(List l) {
for (Object o : l)
System.out.println(o);
}
传入 List<Integer> 会有警告,因为原始类型破坏了类型安全。写成 List<Object> 也不行,因为 List<Integer> 不是 List<Object> 的子类。
解决方案是用通配符 ?:
void printList(List<?> l) {
for (Object o : l)
System.out.println(o);
}
List<?> 表示任何泛型 List 的父类型,可以接收 List<String> 或 List<Integer>。
通配符的限制
对于 List<?>,你可以读取元素(推断为 Object),但不能添加(除了 null):
List<Integer> li = new ArrayList<>();
List<?> lu = li;
System.out.println(lu.get(0)); // 合法,返回 Object
lu.add(new Integer(43)); // 编译错误
编译器不知道 ? 具体是什么类型,无法保证写入安全。但 clear() 等不依赖具体类型参数的方法是允许的。
泛型方法
方法也可以泛型化,即使所在类不是泛型类。例如根据布尔值返回不同参数:
public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}
调用时无需显式指定 T,编译器自动推断:
String s = ifThenElse(true, "a", "b");
选择泛型方法而非类泛型的场景:
- 方法是静态的,无法使用类类型参数。
- 类型约束仅对当前方法有效,简化类签名。
有限制类型
有时需要对类型参数加约束。比如矩阵类只接受数字:
public class Matrix<V extends Number> { ... }
允许 Matrix<Integer>,但不允许 Matrix<String>。若无限制,默认由 Object 限制。这也是为什么 List<?> 的 get() 返回 Object 的原因。

