Skip to content
清晨的一缕阳光
返回

Java 泛型深度解析

Java 泛型深度解析

泛型是 Java 类型系统的重要组成,理解泛型能写出更安全、更简洁的代码。

一、为什么需要泛型

1.1 没有泛型的问题

// ❌ 类型不安全
List list = new ArrayList();
list.add("Hello");
list.add(123);

String str = (String) list.get(0); // 需要强制转换
Integer num = (Integer) list.get(1); // 可能 ClassCastException

// ✅ 使用泛型
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误

String str = list.get(0); // 无需转换,类型安全

1.2 泛型优势

  1. 类型安全:编译期检查类型
  2. 消除强转:无需手动类型转换
  3. 代码复用:一套代码适用于多种类型

二、泛型基础

2.1 泛型类

public class Box<T> {
    private T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get();

2.2 泛型方法

// 泛型方法
public static <T> T getFirst(List<T> list) {
    return list.get(0);
}

// 使用
String first = getFirst(stringList);
Integer firstNum = getFirst(intList);

2.3 泛型接口

public interface Comparable<T> {
    int compareTo(T o);
}

public class Person implements Comparable<Person> {
    private String name;
    
    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}

三、类型擦除

3.1 什么是类型擦除

Java 泛型是伪泛型

编译后泛型信息被擦除

运行时无法获取泛型类型

3.2 擦除规则

// 源码
public class Box<T> {
    private T value;
    public T get() { return value; }
}

// 编译后(擦除)
public class Box {
    private Object value;
    public Object get() { return value; }
}

// 泛型方法
public static <T extends Comparable<T>> T max(T a, T b)

// 擦除后
public static Comparable max(Comparable a, Comparable b)

3.3 擦除的限制

// ❌ 不能使用泛型创建数组
T[] array = new T[10]; // 编译错误

// ✅ 正确做法
Object[] array = new Object[10];
T[] result = (T[]) Arrays.copyOf(array, size, clazz);

// ❌ 不能实例化泛型
T obj = new T(); // 编译错误

// ✅ 使用 Class 参数
public <T> T create(Class<T> clazz) {
    return clazz.newInstance();
}

3.4 泛型不可协变

// ❌ 数组可协变,但不安全
Object[] objects = new String[10];
objects[0] = new Integer(1); // ArrayStoreException

// ❌ 泛型不可协变
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误

// 如果允许:
objects.add(new Integer(1)); // 破坏类型安全

四、通配符

4.1 无界通配符 (?)

// 接受任何类型的 List
public void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 使用
printList(new ArrayList<String>());
printList(new ArrayList<Integer>());

// 限制:只能读(Object),不能写
list.add("Hello"); // 编译错误

4.2 上界通配符 (? extends T)

// 接受 Number 或其子类
public double sum(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) {
        sum += n.doubleValue();
    }
    return sum;
}

// 使用
sum(Arrays.asList(1, 2, 3));        // Integer
sum(Arrays.asList(1.1, 2.2, 3.3));  // Double

// 限制:只能读(T),不能写
list.add(1); // 编译错误

4.3 下界通配符 (? super T)

// 接受 Integer 或其父类
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

// 使用
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());

// 限制:只能写(T),读出来是 Object
Object obj = list.get(0);

五、PECS 原则

5.1 原则定义

PECS = Producer Extends, Consumer Super

- 生产者(只读)用 extends
- 消费者(只写)用 super
- 既读又写不用通配符

5.2 实战示例

// 生产者(只读)
public void printAll(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        System.out.println(animal);
    }
}

// 消费者(只写)
public void addCats(List<? super Cat> cats) {
    cats.add(new Cat("Mimi"));
    cats.add(new Cat("Kitty"));
}

// 既读又写
public void process(List<Animal> animals) {
    Animal animal = animals.get(0); // 读
    animals.add(new Dog());          // 写
}

5.3 记忆技巧

? extends T → 只能读出 T
? super T   → 只能写入 T

extends 向上兼容(子类→父类)
super 向下兼容(父类→子类)

六、泛型最佳实践

6.1 优先使用泛型

// ❌ 原始类型
List list = new ArrayList();

// ✅ 泛型
List<String> list = new ArrayList<>();

6.2 使用菱形操作符

// ❌ 冗余
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();

// ✅ 菱形操作符(JDK 7+)
Map<String, List<Integer>> map = new HashMap<>();

6.3 泛型方法优于泛型类

// ❌ 泛型类
class Collections {
    public static <T> List<T> emptyList(Class<T> clazz) {
        return new ArrayList<>();
    }
}

// ✅ 泛型方法
class Collections {
    public static <T> List<T> emptyList() {
        return new ArrayList<>();
    }
}

6.4 类型安全的异构容器

// 使用 Class 作为 key
public class Container {
    private Map<Class<?>, Object> data = new HashMap<>();
    
    public <T> void put(Class<T> type, T value) {
        data.put(type, value);
    }
    
    public <T> T get(Class<T> type) {
        return type.cast(data.get(type));
    }
}

// 使用
Container container = new Container();
container.put(String.class, "Hello");
container.put(Integer.class, 123);

String str = container.get(String.class);
Integer num = container.get(Integer.class);

七、常见误区

7.1 泛型数组

// ❌ 错误
List<String>[] array = new ArrayList[10];

// ✅ 正确
List<List<String>> list = new ArrayList<>();

7.2 泛型与异常

// ❌ 不能捕获泛型异常
try {
    // ...
} catch (T e) { // 编译错误
}

// ✅ 正确
public <T extends Throwable> void handle(Class<T> clazz) throws Throwable {
    try {
        // ...
    } catch (Throwable e) {
        if (clazz.isInstance(e)) {
            throw clazz.cast(e);
        }
    }
}

7.3 泛型与 instanceof

// ❌ 不能检查泛型类型
if (obj instanceof List<String>) { // 编译错误
}

// ✅ 正确
if (obj instanceof List) {
    List<?> list = (List<?>) obj;
}

八、总结

泛型核心要点:

概念说明示例
类型擦除编译后泛型信息丢失List<String>List
**? **(无界)任何类型List<?>
? extends TT 或其子类(只读)List<? extends Number>
? super TT 或其父类(只写)List<? super Integer>
PECS生产者 extends,消费者 super-

泛型是类型安全的保障,掌握 PECS 原则能正确使用通配符。


分享这篇文章到:

上一篇文章
反射与动态代理详解
下一篇文章
Go语言基础