Skip to content

泛型

泛型简介

为什么要使用泛型?

┌─────────────────────────────────────────────────────────────────┐
│                      泛型解决的问题                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ❌ 没有泛型时                                                    │
│  ─────────────────────                                          │
│  List list = new ArrayList();                                    │
│  list.add("hello");                                              │
│  list.add(123);                                                  │
│  String str = (String) list.get(1); // ClassCastException!      │
│                                                                  │
│  ✅ 有泛型后                                                      │
│  ─────────────────────                                          │
│  List<String> list = new ArrayList<String>();                    │
│  list.add("hello");                                              │
│  // list.add(123);  // 编译错误!                               │
│  String str = list.get(0); // 无需强转,安全!                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

泛型的好处

  1. 类型安全 - 编译时检查,减少运行时 ClassCastException
  2. 消除强制类型转换 - 代码更简洁
  3. 提高代码可读性 - 一目了然知道集合里放什么类型

泛型类

基本定义

java
// 泛型类 - T 是类型参数,可以是任意标识符
public class Box<T> {
    private T value;

    public Box() { }

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
String value = stringBox.getValue(); // 无需强转

Box<Integer> intBox = new Box<>(123);
Integer num = intBox.getValue();

多参数泛型

java
// 多个类型参数
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

// 使用
Pair<String, Integer> pair = new Pair<>("age", 25);
Pair<String, List<Integer>> complexPair = new Pair<>("numbers",
    new ArrayList<>(Arrays.asList(1, 2, 3)));

泛型接口

java
// 泛型接口
public interface Container<T> {
    void add(T item);
    T get(int index);
    int size();
}

// 实现方式1:指定具体类型
public class StringContainer implements Container<String> {
    private List<String> list = new ArrayList<>();

    @Override
    public void add(String item) {
        list.add(item);
    }

    @Override
    public String get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }
}

// 实现方式2:保留泛型
public class GenericContainer<T> implements Container<T> {
    private List<T> list = new ArrayList<>();

    @Override
    public void add(T item) {
        list.add(item);
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }
}

泛型方法

基本语法

java
public class GenericMethodDemo {

    // 泛型方法 - 修饰符后返回值前声明 <T>
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }

    // 泛型方法可以有多个类型参数
    public static <K, V> void printPair(Pair<K, V> pair) {
        System.out.println(pair.getKey() + ": " + pair.getValue());
    }

    // 返回类型也可以是泛型
    public static <T> T getFirst(T[] array) {
        if (array.length == 0) {
            return null;
        }
        return array[0];
    }
}

// 调用
String[] strs = {"a", "b", "c"};
Integer[] nums = {1, 2, 3};

GenericMethodDemo.printArray(strs);  // 推导类型
GenericMethodDemo.printArray(nums);

类型限定(Bounded Type)

上限限定

java
// T 必须是 Number 或其子类
public class NumberBox<T extends Number> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public double toDouble() {
        return value.doubleValue();  // 可以调用 Number 的方法
    }
}

// 使用
NumberBox<Integer> intBox = new NumberBox<>();
intBox.setValue(10);
double num = intBox.toDouble();

NumberBox<Double> doubleBox = new NumberBox<>();
doubleBox.setValue(3.14);

// NumberBox<String> strBox;  // 编译错误,String 不是 Number 子类

多重限定

java
// T 必须同时实现 Comparable 和 Serializable
public class ComparableBox<T extends Comparable<T> & Serializable> {
    private T value;

    public T getMax(T other) {
        return value.compareTo(other) > 0 ? value : other;
    }
}

泛型通配符

三种通配符

通配符名称特点使用场景
<?>无限定通配符可以接收任意类型只读不写
<? extends T>上限通配符可以接收 T 及其子类读取数据(Producer)
<? super T>下限通配符可以接收 T 及其父类写入数据(Consumer)

示例详解

java
// ? extends - 生产者(读取)
public static double sumOfList(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) {  // 可以读取为 Number
        sum += n.doubleValue();
    }
    // list.add(1);  // 编译错误!不能写入
    return sum;
}

// ? super - 消费者(写入)
public static void addNumbers(List<? super Integer> list) {
    list.add(1);    // 可以写入 Integer
    list.add(2);
    // Integer num = list.get(0);  // 读取类型是 Object
}

// 无限定通配符
public static void printList(List<?> list) {
    for (Object obj : list) {  // 只能读取为 Object
        System.out.println(obj);
    }
    // list.add("test");  // 编译错误!不能写入(除了 null)
}

💡 面试高频点:PECS 原则

┌─────────────────────────────────────────────────────────────────┐
│                        PECS 原则                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Producer Extends - 如果只读取数据,用 extends                    │
│  ───────────────────────────────────                            │
│  List<? extends Number> list = new ArrayList<Integer>();        │
│  Number num = list.get(0);  // ✅ 读取安全                       │
│                                                                  │
│  Consumer Super - 如果只写入数据,用 super                        │
│  ─────────────────────────────────                              │
│  List<? super Integer> list = new ArrayList<Number>();          │
│  list.add(1);  // ✅ 写入安全                                   │
│                                                                  │
│  如果既要读又要写,不要用通配符                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

类型擦除

什么是类型擦除?

Java 的泛型是编译时特性,运行时不保留泛型信息(向后兼容 Java 5 之前的代码)。

java
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

// 运行时会认为是同一个类
System.out.println(stringList.getClass() == intList.getClass()); // true

擦除规则

┌─────────────────────────────────────────────────────────────────┐
│                      类型擦除规则                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 无限定类型参数 → 擦除为 Object                              │
│     public class Box<T> { }                                     │
│     → 编译后:public class Box { Object value; }                │
│                                                                  │
│  2. 有限定类型参数 → 擦除为第一个边界类型                          │
│     public class NumberBox<T extends Number> { }                │
│     → 编译后:public class NumberBox { Number value; }          │
│                                                                  │
│  3. 多重限定 → 擦除为第一个满足的边界类型                         │
│     T extends Comparable & Serializable                          │
│     → 编译后:Comparable(第一个边界)                           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

泛型擦除带来的问题

java
// ❌ 编译错误:无法创建泛型数组
public class GenericArray<T> {
    T[] array = new T[10];  // 编译错误!
}

// ✅ 解决方案:使用反射或Object数组
public class GenericArray<T> {
    T[] array;

    @SuppressWarnings("unchecked")
    public GenericArray(Class<T> type, int size) {
        this.array = (T[]) Array.newInstance(type, size);
    }
}

// ❌ 编译错误:类型参数不能用于 instanceof
if (obj instanceof List<String>) { }  // 编译错误!

// ✅ 可以使用无限定通配符
if (obj instanceof List<?>) { }

泛型与重载

泛型方法重载

java
// ❌ 编译错误:擦除后方法签名相同
public class Example {
    public void method(List<String> list) { }
    public void method(List<Integer> list) { }  // 编译错误!
}

// ✅ 可以通过泛型方法重载
public class Example {
    public <T> void method(List<T> list) { }
    public <T> void method(T[] array) { }
}

泛型的实际应用

工具类中的泛型

java
// 通用排序方法
public class SortUtils {
    public static <T extends Comparable<T>> void sort(List<T> list) {
        Collections.sort(list);
    }

    public static <T> void swap(List<T> list, int i, int j) {
        T temp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, temp);
    }
}

// 通用构建器
public class Builder<T> {
    private T value;

    public Builder<T> with(T value) {
        this.value = value;
        return this;
    }

    public T build() {
        return value;
    }
}

返回值泛型

java
// 通用响应对象
public class Response<T> {
    private int code;
    private String message;
    private T data;

    public static <T> Response<T> success(T data) {
        Response<T> response = new Response<>();
        response.code = 200;
        response.data = data;
        return response;
    }

    public static <T> Response<T> error(String message) {
        Response<T> response = new Response<>();
        response.code = 500;
        response.message = message;
        return response;
    }
}

// 使用
Response<String> resp1 = Response.success("操作成功");
Response<User> resp2 = Response.success(new User());
Response<List<Order>> resp3 = Response.success(orderList);

总结

泛型核心知识点

知识点面试频率实战重要性
泛型类/接口定义⭐⭐⭐⭐⭐⭐⭐⭐
泛型方法⭐⭐⭐⭐⭐⭐⭐⭐⭐
通配符 extends/super⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
PECS 原则⭐⭐⭐⭐⭐⭐⭐⭐⭐
类型擦除⭐⭐⭐⭐⭐⭐⭐⭐⭐

⚠️ 易错点提醒

  1. 泛型不能直接实例化 new T()
  2. 泛型不能用于 static 字段
  3. 泛型不能用于 instanceof(可以用 instanceof List<?>
  4. List<String> 不是 List<Object> 的子类
  5. 通配符 ? extends T 不能写入,? super T 不能精确读取