跳转至

13 泛型

本章目标

  • 理解泛型:让代码支持多种类型且安全
  • 掌握泛型类、泛型方法
  • 掌握通配符 ? extends / ? super
  • 了解类型擦除

13.1 什么是泛型?

泛型(Generic) 就是"类型参数化":写代码时用占位符 T,使用时指定具体类型。

// 一个箱子类,什么类型都能装,且安全
public class Box<T> {
    private T content;

    public void put(T content) {
        this.content = content;
    }

    public T get() {
        return content;
    }
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.put("宝剑");
String sword = stringBox.get();    // 不用强转,类型安全

Box<Integer> intBox = new Box<>();
intBox.put(100);
int value = intBox.get();

如果不写泛型(Object),取出来全是 Object,要强转,还容易转错:

Box box = new Box();           // 裸类型
box.put("hello");
box.put(123);                  // 什么都能放
Integer n = (Integer) box.get();  // ❌ 强转出错 ClassCastException

13.2 泛型类

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> p = new Pair<>("level", 30);

13.3 泛型方法

public class Utils {
    // 泛型方法:<T> 写在返回值前
    public static <T> T firstOrNull(List<T> list) {
        return list.isEmpty() ? null : list.get(0);
    }

    public static <T> List<T> copyToList(T... items) {
        return new ArrayList<>(List.of(items));
    }
}

// 调用(类型自动推断)
String s = Utils.firstOrNull(List.of("a", "b"));
Integer i = Utils.firstOrNull(List.of(1, 2, 3));

13.4 通配符 ?

只读上界:? extends T

"这是 T 或 T 的子类的集合",可以读,不能往里放:

static double totalArea(List<? extends Shape> shapes) {
    double sum = 0;
    for (Shape s : shapes) {     // 可以读,按 Shape 处理
        sum += s.area();
    }
    return sum;
}

List<Circle> circles = new ArrayList<>();
totalArea(circles);    // ✅ List<Circle> 能传进来

只写下界:? super T

"这是 T 或 T 的父类的集合",可以放 T,读出来是 Object:

static void addNumbers(List<? super Integer> list) {
    list.add(1);       // ✅ 可以放 Integer
    list.add(2);
}

List<Number> nums = new ArrayList<>();
addNumbers(nums);      // ✅

记忆口诀:PECS

Producer Extends,Consumer Super——生产者用 extends(只读),消费者用 super(只写)。

13.5 类型擦除(Type Erasure)

泛型是编译期的概念。编译后 Box<String> 和 Box<Integer> 是同一个类,类型信息被擦除:

Box<String> a = new Box<>();
Box<Integer> b = new Box<>();
a.getClass() == b.getClass();   // true!运行时都是 Box.class

由此带来几个限制:

  1. 不能 new T():new T() 编译错误(运行时不知道 T 是什么)
  2. 不能创建泛型数组:T[] arr = new T[10] 编译错误
  3. 不能 instanceof T:obj instanceof T 编译错误
public class Container<T> {
    public void create() {
        // new T();        // ❌ 编译错误
    }
}

13.6 实战:模组场景

// 简化的方块实体数据缓存:按类型存数据
public class DataCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();

    public void set(K key, V value) { cache.put(key, value); }

    public V get(K key) { return cache.get(key); }

    public V getOrDefault(K key, V def) { return cache.getOrDefault(key, def); }
}

// 使用
DataCache<String, Integer> blockStates = new DataCache<>();
blockStates.set("furnace:lit", 1);

13.7 小练习

  1. 写泛型类 Stack<T>:push / pop / isEmpty
  2. 写泛型方法 swap(List<T> list, int i, int j) 交换两个元素
  3. 写方法 printAll(List<? extends Number>),打印所有数字

本章小结

  • 泛型 = 类型参数化,编译期检查,运行期擦除
  • 泛型类/泛型方法语法:<T> 占位
  • 通配符:? extends T 只读、? super T 只写(PECS)
  • 不能 new T、不能泛型数组、不能 instanceof T

下一章:14 内部类与匿名类

🏠 返回总站