Java泛型原理与实战应用
Java泛型是JDK 1.5引入的核心特性之一,它在编译期提供类型安全检查,避免运行时ClassCastException,同时提升代码复用性。尽管泛型在运行时被擦除(Type Erasure),但其设计思想深刻影响了现代Java集合框架与工具类的实现方式。
泛型类:统一类型抽象
传统方式中,若需为不同数据类型实现相同逻辑(如打印器),往往需要重复编写多个类:
public class StringPrinter {
private String data;
public void print() { System.out.println(data); }
}
public class IntegerPrinter {
private Integer data;
public void print() { System.out.println(data); }
}
泛型提供了一种更优雅的解决方案:
public class Box<T> {
private T content;
public Box(T item) { this.content = item; }
public T getContent() { return content; }
public void setContent(T item) { this.content = item; }
public void display() {
System.out.println("内容: " + content);
}
}
使用时,类型实参在实例化时传入:
Box<String> strBox = new Box<>("Hello");
Box<Double> numBox = new Box<>(3.14);
strBox.display(); // 输出: 内容: Hello
numBox.display(); // 输出: 内容: 3.14
JDK 7引入了"菱形运算符"简化语法:
Box<String> box = new Box<>("Java");
类型边界:约束泛型范围
有时需限制泛型类型必须是某个类的子类或实现特定接口。使用extends关键字设定上限:
public class NumberBox<T extends Number> {
private T value;
public NumberBox(T val) { this.value = val; }
public double doubleValue() {
return value.doubleValue(); // 可安全调用Number的方法
}
}
可同时绑定多个接口:
public class ComparableBox<T extends Number & Comparable<T>> {
private T value;
public ComparableBox(T val) { this.value = val; }
public T max(ComparableBox<T> other) {
return value.compareTo(other.value) > 0 ? value : other.value;
}
}
⚠️ 注意:
extends仅用于类或接口上限,不能使用super作为类型参数的下限,这会导致类型系统不一致。
异常类不能声明为泛型,因为运行时异常处理依赖具体类型,而泛型在运行时已被擦除:
// 编译失败:Generic class may not extend 'java.lang.Throwable'
public class GenericException<T> extends Exception { }
泛型方法:独立于类的类型参数
并非所有泛型逻辑都需要类级别声明。方法可独立声明类型参数:
public class Utility {
// 泛型方法:返回数组中中间元素
public static <T> T getMiddle(T[] array) {
if (array == null || array.length == 0) return null;
return array[array.length / 2];
}
// 静态泛型方法:将数组转为List
public static <T> List<T> toList(T[] items) {
List<T> list = new ArrayList<>();
for (T item : items) {
list.add(item);
}
return list;
}
}
调用时,编译器自动推断类型:
String[] names = {"Alice", "Bob", "Charlie"};
String mid = Utility.getMiddle(names); // 推断T为String
Integer[] nums = {1, 2, 3, 4, 5};
List<Integer> numList = Utility.toList(nums);
若需显式指定类型,必须在方法名前使用类名或对象引用:
String result = Utility.<String>getMiddle(new String[]{"x", "y"});
泛型构造器
构造器同样可声明独立类型参数:
public class Container<T> {
private T data;
// 泛型构造器:与类泛型无关
public <K> Container(K value) {
this.data = (T) value; // 强制转换,运行时可能出错
}
public T getData() { return data; }
}
注意:泛型构造器与类泛型可以共存,但需谨慎处理类型推断冲突:
Container<String> c1 = new Container<>("test"); // 类型T=String,构造器K=String
Container<Integer> c2 = new Container<>(123); // 类型T=Integer,构造器K=Integer
// 显式指定构造器类型(不推荐,易混淆)
Container<String> c3 = new <String>Container<>(456); // 编译通过,但T仍为String
类型擦除与运行时行为
Java泛型仅存在于编译期,运行时所有泛型信息均被擦除,替换为边界类型(如Object或指定上限):
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(stringList.getClass() == intList.getClass()); // true
因此,以下操作在运行时是合法的(通过反射绕过编译检查):
List<String> strings = new ArrayList<>();
strings.add("Safe");
// 利用反射添加非String类型
Method addMethod = strings.getClass().getMethod("add", Object.class);
addMethod.invoke(strings, 42);
addMethod.invoke(strings, new Date());
for (Object obj : strings) {
System.out.println(obj); // 输出: Safe, 42, Fri Apr 25 22:30:10 CST 2025
}
由于类型擦除,方法签名不能仅因泛型不同而重载:
// 编译错误:两个方法擦除后签名相同
void process(List<String> list) { }
void process(List<Integer> list) { } // ❌ 与上一个方法冲突
通配符:灵活的类型协变与逆变
泛型类型不具备继承性。即使Cat extends Animal,List<Cat>也不是List<Animal>的子类。
List<Animal> animals = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
// animals = cats; // ❌ 编译错误
为解决此问题,引入通配符?:
- 无界通配符
List<?>:表示任意类型列表,可读不可写(除null)
public void printAll(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
// list.add("test"); // ❌ 编译错误
list.add(null); // ✅ 允许
}
- 上界通配符
List<? extends Animal>:只读,可安全获取为Animal类型
public void feedAnimals(List<? extends Animal> animals) {
for (Animal a : animals) {
a.eat(); // 安全调用
}
// animals.add(new Dog()); // ❌ 不能写入,类型未知
}
- 下界通配符
List<? super Cat>:可写入Cat及其子类,读取时只能作为Object
public void addCats(List<? super Cat> container) {
container.add(new Cat()); // ✅
container.add(new YingDuan()); // ✅ 继承自Cat
container.add(new Dog()); // ❌ 不是Cat的子类
Object obj = container.get(0); // ✅ 读取为Object
}
通配符与泛型类的组合使用
当泛型类本身有边界时,通配符仍需遵守其定义:
public class Pet<T extends Animal> {
private T pet;
public void setPet(T p) { this.pet = p; }
public T getPet() { return pet; }
}
Pet<? extends Cat> myCat = new Pet<YingDuan>();
myCat.setPet(new Cat()); // ❌ 编译失败:类型不匹配
Cat c = myCat.getPet(); // ✅ 返回Cat类型
✅ 记忆口诀:
? extends T→ 只读,产出T类型? super T→ 只写,接受T及子类,产出Object- 类型参数
T extends T→ 确定类型,可读可写T super T→ Java不支持,语法错误