Java 泛型核心机制与实战指南
泛型是 Java 语言中实现类型安全的重要特性。它允许我们在编译阶段就检查类型,避免运行时的强制转换错误,同时提升代码的可复用性。
泛型类
基本语法
定义泛型类时,需要在类名后使用尖括号声明类型参数:
class ClassName<T1, T2, ..., Tn> {
// 这里可以使用类型参数
}
如果需要继承,也可以指定父类的泛型参数:
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
实际应用
实例化泛型类时,必须指定具体的类型实参。这能让编译器知道容器里装的是什么。
class MyArray<T> {
public Object[] array = new Object[10];
public void set(int pos, T val) {
array[pos] = val;
}
public T get(int pos) {
return (T) array[pos];
}
public Object[] getArray() {
return array;
}
}
public class Test {
public static void main(String[] args) {
MyArray<String> myArray = new MyArray<>();
// 后续操作会自动进行类型检查
}
}
泛型上界
有时候我们需要对传入的类型变量做约束,比如要求它必须是某个类的子类。这时可以通过 extends 关键字来设定边界。
定义与使用
class MyArray<T extends Number> {
public Object[] array = new Object[10];
public void set(int pos, T val) { array[pos] = val; }
public T get(int pos) { return (T) array[pos]; }
}
public class Test {
public static void main(String[] args) {
// MyArray<String> myArray1 = new MyArray<>(); // 编译错误,String 不是 Number 的子类型
MyArray<Integer> myArray = new MyArray<>(); // 正常,Integer 是 Number 的子类型
}
}
一个典型的例子是寻找数组中的最大值,这需要元素具备比较能力:
class Alg<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
类型擦除
理解泛型的关键在于'类型擦除'。Java 的泛型是在编译期实现的,JVM 在运行时并不保留泛型信息。这意味着 List<String> 和 List<Integer> 在运行时都被视为原始类型 List。
编译器在类型擦除阶段主要做了以下几件事:
- 移除类型参数:字节码中不再包含泛型参数信息。
- 替换为原始类型或上界:如果没有指定上界,泛型参数会被替换为
Object;如果指定了(如T extends Comparable<T>),则替换为上界类型。 - 插入类型转换:为了保持类型安全,编译器会在读取元素的地方自动插入从
Object到具体类型的强制转换。 - 生成桥接方法:为了保证与旧版本非泛型代码的兼容性,编译器可能会生成桥接方法。
这种机制确保了泛型代码能与非泛型代码无缝对接,但也带来了一些限制,比如无法在运行时直接获取泛型的具体类型。
通配符
当不确定具体类型,或者需要更灵活的接口设计时,可以使用通配符 ?。
class Message<T> {
private T message;
public T getMessage() { return message; }
public void setMessage(T message) { this.message = message; }
}
public class Test {
// 可以传入任意类型的 Message
public static void fun(Message<?> temp) {
System.out.println(temp.getMessage());
}
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setMessage("你好!");
fun(message);
Message<Integer> message2 = new Message<>();
message2.setMessage(199);
fun(message2);
}
}
通配符上界 (? extends T)
主要用于读取数据。表示泛型参数必须是 T 或其子类。
- 特点:不能添加任何元素(除了
null),因为编译器无法确定具体是哪一个子类,写入可能导致类型不安全。 - 读取:返回类型为
T或其父类(通常是Object)。
class Food { }
class Fruit extends Food { }
class Apple extends Fruit { }
class Banana extends Fruit { }
class Message<T> {
private T message;
public T getMessage() { return message; }
public void setMessage(T message) { this.message = message; }
}
public class Test1 {
public static void funExtends(Message<? extends Fruit> temp) {
System.out.println(temp.getMessage());
// temp.setMessage(new Banana()); // 编译错误,无法确定具体类型
Fruit fruit = temp.getMessage(); // 安全读取
}
}
通配符下界 (? super T)
主要用于写入数据。表示泛型参数必须是 T 或其父类。
- 特点:可以添加
T或其子类的元素,因为这些类型都兼容下界。 - 读取:只能确保是
Object类型,因为可能是父类的任意一种。
class Plate<T> {
private T plate;
public T getPlate() { return plate; }
public void setPlate(T plate) { this.plate = plate; }
}
public class Test1 {
public static void funSuper(Plate<? super Fruit> temp) {
// 此时可以修改,添加的是 Fruit 或者 Fruit 的子类
temp.setPlate(new Apple());
temp.setPlate(new Banana());
System.out.println(temp.getPlate()); // 只能输出,无法强转为 Fruit
}
}
泛型方法
泛型方法可以在调用时独立于类定义泛型参数。通常用于工具类方法。
class Alg2 {
// 泛型方法,<T> 放在返回值类型之前
public static <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
public class Test2 {
public static void main(String[] args) {
Integer[] array = {1, 13, 51, 71, 19};
Integer ret = Alg2.<Integer>findMax(array);
System.out.println(ret);
}
}
泛型的限制
虽然泛型很强大,但在使用时仍需注意以下限制:
- 不支持基本数据类型:泛型只能使用引用类型。需要使用
int时请用Integer。 - 不能创建泛型数组:由于类型擦除,
new List<String>[10]是不允许的。建议使用ArrayList<List<String>>替代。 - 不能实例化泛型对象:
new List<String>()是错误的,应使用具体实现类如new ArrayList<>()。 - 运行时类型查询受限:由于擦除,
instanceof和getClass()无法检测泛型参数的具体类型。 - 泛型类型参数无继承关系:即使
A是B的子类,List<A>也不是List<B>的子类。 - 反射限制:反射无法直接获取泛型类型参数的具体信息。
掌握这些规则,能帮助你在编写 Java 代码时更好地利用泛型,既保证类型安全,又避免常见的坑。


