3Effective Java(第3版)

泛型

泛型编程技巧

类型擦除通配符PECS原则泛型方法

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章:泛型(Generics)

一、导读

1.1 本章学习目标

第3章聚焦于 Java 泛型系统——Java 5 引入的最重要的语言特性之一。泛型提供了编译时类型安全,让你能够编写更通用、更安全的代码。本章通过8条经验法则,帮助你掌握泛型编程的精髓,避免常见的类型陷阱。

本章的核心学习目标包括:

  • 理解类型擦除的本质:掌握 Java 泛型的实现机制,理解为什么泛型类型在运行时不存在。
  • 掌握通配符的使用:理解 ? extends T 和 ? super T 的区别,掌握 PECS 原则。
  • 学会编写类型安全的泛型方法:掌握泛型方法的定义和调用,理解类型推断。
  • 理解泛型与数组的冲突:为什么不能创建泛型数组,如何用 List 替代。
  • 掌握泛型类的最佳实践:学会设计类型安全的泛型类和集合。

1.2 为什么第三章如此重要

泛型是 Java 类型系统的核心扩展。没有泛型,你就无法编写类型安全的集合、框架和库。泛型的引入使得许多在运行时才能发现的类型错误可以在编译时被捕获,大大提高了代码的可靠性和可读性。

然而,Java 的泛型实现(类型擦除)带来了一些令人困惑的限制。理解这些限制的本质,才能正确地使用泛型,避免常见的陷阱。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

为什么 List<String> 不是 List<Object> 的子类型?泛型为什么是不可变的(invariant)?

类型擦除意味着什么?它在运行时造成了哪些限制?

PECS 原则是什么?什么时候用 ? extends T,什么时候用 ? super T?

为什么不能创建泛型数组?List 如何替代数组?

如何编写类型安全的异构容器(typesafe heterogeneous container)?


二、核心概念详解

3.1 消除非受检警告(Item 26)

使用泛型时,编译器可能会产生"非受检"(unchecked)警告。这些警告通常意味着代码不是类型安全的,应该尽量消除。

java
// 反模式:使用原始类型
Set rawSet = new HashSet();
rawSet.add("hello");
rawSet.add(42);  // 编译通过,但运行时可能出错

// 正确做法:使用参数化类型
Set<String> typedSet = new HashSet<>();
typedSet.add("hello");
// typedSet.add(42);  // 编译错误!

关键规则:

  • 消除每一个 unchecked 警告,或者证明它是类型安全的。
  • 不要使用 @SuppressWarnings("unchecked") 来压制警告,除非你能证明代码是类型安全的。
  • 如果必须使用 @SuppressWarnings,添加注释说明为什么是安全的。

3.2 列表优于数组(Item 28)

3.2.1 数组与泛型的冲突

数组和泛型有两个不兼容的特性:

数组是协变的(covariant):如果 Sub 是 Super 的子类型,那么 Sub[] 是 Super[] 的子类型。

泛型是不可变的(invariant):对于任意两个不同类型 Type1 和 Type2,List<Type1> 既不是 List<Type2> 的子类型,也不是其超类型。

java
// 数组是协变的——运行时检查
Object[] objectArray = new Long[1];
objectArray[0] = "Hello";  // 编译通过,但运行时抛出 ArrayStoreException

// 泛型是不可变的——编译时检查
List<Object> objectList = new ArrayList<Long>();  // 编译错误!
// 不允许 Long 是 Object 的子类型就认为 List<Long> 是 List<Object> 的子类型

3.2.2 不能创建泛型数组

java
// 编译错误:不能创建参数化类型的数组
List<String>[] stringLists = new List<String>[1];

// 为什么?因为数组是协变的,而泛型是不可变的
// 如果允许创建泛型数组,会导致类型安全问题:
Object[] objects = stringLists;  // 如果允许
objects[0] = Arrays.asList(42);  // 数组存储检查无法捕获这个错误
String s = stringLists[0].get(0);  // ClassCastException!

3.2.3 用 List 替代数组

java
// 反模式:使用泛型数组
public class Chooser<T> {
    private T[] choiceArray;

    public Chooser(Collection<T> choices) {
        choiceArray = (T[]) choices.toArray();  // unchecked warning
    }
}

// 正确做法:使用 List
public class Chooser<T> {
    private final List<T> choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }
}

3.3 优先考虑泛型(Item 29)

3.3.1 泛型类的优势

泛型类让你能够编写类型安全的通用代码:

java
// 反模式:使用 Object
public class Stack {
    private Object[] elements;
    private int size = 0;

    public Stack(int initialCapacity) {
        elements = new Object[initialCapacity];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
}

// 使用时需要强制转换
Stack stack = new Stack(10);
stack.push("hello");
String s = (String) stack.pop();  // 需要强制转换,可能出错

// 正确做法:使用泛型
public class Stack<T> {
    private Object[] elements;
    private int size = 0;

    @SuppressWarnings("unchecked")
    public Stack(int initialCapacity) {
        elements = new Object[initialCapacity];
    }

    public void push(T e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public T pop() {
        if (size == 0) throw new EmptyStackException();
        @SuppressWarnings("unchecked")
        T result = (T) elements[--size];
        elements[size] = null;
        return result;
    }
}

// 使用时类型安全
Stack<String> stack = new Stack<>(10);
stack.push("hello");
String s = stack.pop();  // 不需要强制转换

3.4 优先考虑泛型方法(Item 30)

3.4.1 泛型方法的定义

java
// 静态泛型方法
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

// 使用——类型推断
Set<Integer> ints = Set.of(1, 2, 3);
Set<Integer> moreInts = Set.of(3, 4, 5);
Set<Integer> union = union(ints, moreInts);  // 类型自动推断

// 泛型方法
public static <E> List<E> reverse(List<E> list) {
    List<E> result = new ArrayList<>(list);
    Collections.reverse(result);
    return result;
}

3.4.2 泛型单例工厂

java
// 泛型单例工厂
public interface UnaryFunction<T> {
    T apply(T arg);
}

// 不可变对象可以共享
private static UnaryFunction<Object> IDENTITY_FUNCTION = arg -> arg;

@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction() {
    return (UnaryFunction<T>) IDENTITY_FUNCTION;
}

// 使用
UnaryFunction<String> stringFn = identityFunction();
UnaryFunction<Number> numberFn = identityFunction();

System.out.println(stringFn.apply("hello"));  // hello
System.out.println(numberFn.apply(42));        // 42

3.5 利用有限制通配符来提升 API 的灵活性(Item 31)

3.5.1 PECS 原则

PECS 代表 Producer Extends, Consumer Super:

  • 如果参数化类型代表一个 T 的生产者(producer),使用 <? extends T>。
  • 如果参数化类型代表一个 T 的消费者(consumer),使用 <? super T>。
java
// Producer Extends:从集合中读取元素
public static <E extends Comparable<E>> E max(Collection<? extends E> list) {
    if (list.isEmpty()) throw new IllegalArgumentException("Empty collection");

    E result = null;
    for (E e : list) {
        if (result == null || e.compareTo(result) > 0) {
            result = e;
        }
    }
    return result;
}

// 使用
List<Integer> ints = List.of(1, 2, 3, 4, 5);
System.out.println(max(ints));  // 5

List<String> strings = List.of("hello", "world", "java");
System.out.println(max(strings));  // world

// Consumer Super:向集合中写入元素
public static <T> void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

// 使用
List<Number> numbers = new ArrayList<>();
addNumbers(numbers);  // List<Number> 是 Integer 的超类型
System.out.println(numbers);  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

3.5.2 同时生产者和消费者

如果一个参数既是生产者又是消费者,通配符对你没有帮助——你需要使用精确类型:

java
// 既读取又写入
public static <E> void swap(List<E> list, int i, int j) {
    E tmp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, tmp);
}

3.5.3 通配符捕获

有时编译器无法推断通配符类型,需要使用辅助方法:

java
// 通配符捕获
public static void swapRandom(List<?> list, int i, int j) {
    swapHelper(list, i, j);
}

private static <E> void swapHelper(List<E> list, int i, int j) {
    E tmp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, tmp);
}

3.6 优先考虑类型安全的异构容器(Item 33)

3.6.1 什么是异构容器

普通的泛型容器(如 List<E>)只能存储一种类型的元素。类型安全的异构容器允许存储多种类型的元素,同时保持类型安全。

java
// 类型安全的异构容器
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

// 使用
Favorites f = new Favorites();
f.putFavorite(String.class, "Hello");
f.putFavorite(Integer.class, 42);
f.putFavorite(Class.class, Favorites.class);

String s = f.getFavorite(String.class);       // "Hello"
Integer i = f.getFavorite(Integer.class);     // 42
Class<?> c = f.getFavorite(Class.class);      // Favorites.class

System.out.println(s);
System.out.println(i);
System.out.println(c.getName());

3.6.2 类型令牌(Type Token)

Class<T> 对象被称为类型令牌(type token)。它携带了类型信息,使得 Favorites 能够在运行时保持类型安全。

java
// 类型令牌的局限
// 不能用于不可具体化的类型(non-reifiable types)
// f.putFavorite(List<String>.class, List.of("a", "b"));  // 编译错误

// 解决方案:使用 TypeReference(需要第三方库如 Guava)
// TypeReference<List<String>> typeRef = new TypeReference<List<String>>() {};

三、实战案例

3.1 泛型工具类实战

java
public class CollectionUtils {

    // 泛型方法:安全地获取列表元素
    public static <T> T getOrNull(List<T> list, int index) {
        if (list == null || index < 0 || index >= list.size()) {
            return null;
        }
        return list.get(index);
    }

    // 泛型方法:安全地获取 Map 值
    public static <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) {
        V value = map.get(key);
        return value != null ? value : defaultValue;
    }

    // 泛型方法:批量转换
    public static <F, T> List<T> transform(
            Collection<F> from,
            Function<F, T> transformer) {
        List<T> result = new ArrayList<>(from.size());
        for (F element : from) {
            result.add(transformer.apply(element));
        }
        return result;
    }

    // 泛型方法:过滤
    public static <T> List<T> filter(
            Collection<T> collection,
            Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T element : collection) {
            if (predicate.test(element)) {
                result.add(element);
            }
        }
        return result;
    }

    // 泛型方法:分组
    public static <T, K> Map<K, List<T>> groupBy(
            Collection<T> collection,
            Function<T, K> classifier) {
        Map<K, List<T>> result = new LinkedHashMap<>();
        for (T element : collection) {
            K key = classifier.apply(element);
            result.computeIfAbsent(key, k -> new ArrayList<>()).add(element);
        }
        return result;
    }
}

// 使用
List<String> names = List.of("Alice", "Bob", "Charlie", "David");

// 转换
List<Integer> lengths = CollectionUtils.transform(names, String::length);
System.out.println(lengths);  // [5, 3, 7, 5]

// 过滤
List<String> longNames = CollectionUtils.filter(names, n -> n.length() > 4);
System.out.println(longNames);  // [Alice, Charlie, David]

// 分组
List<String> words = List.of("apple", "banana", "cherry", "apricot", "blueberry");
Map<Character, List<String>> grouped = CollectionUtils.groupBy(words, w -> w.charAt(0));
System.out.println(grouped);  // {a=[apple, apricot], b=[banana, blueberry], c=[cherry]}

3.2 泛型栈实现

java
public class GenericStack<T> {
    private Object[] elements;
    private int size;

    public GenericStack() {
        this(16);
    }

    @SuppressWarnings("unchecked")
    public GenericStack(int initialCapacity) {
        if (initialCapacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive");
        }
        elements = new Object[initialCapacity];
        size = 0;
    }

    public void push(T element) {
        ensureCapacity();
        elements[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public T pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        T result = (T) elements[--size];
        elements[size] = null;
        return result;
    }

    @SuppressWarnings("unchecked")
    public T peek() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        return (T) elements[size - 1];
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (size == elements.length) {
            elements = Arrays.copyOf(elements, size * 2);
        }
    }

    // 支持通配符的批量操作
    public void pushAll(Iterable<? extends T> src) {
        for (T e : src) {
            push(e);
        }
    }

    public void popAll(Collection<? super T> dst) {
        while (size > 0) {
            dst.add(pop());
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("Stack[");
        for (int i = 0; i < size; i++) {
            if (i > 0) sb.append(", ");
            sb.append(elements[i]);
        }
        sb.append("]");
        return sb.toString();
    }
}

// 使用
GenericStack<Number> numberStack = new GenericStack<>();

// pushAll 接受 Number 的子类型
numberStack.pushAll(List.of(1, 2.0, 3L));

// popAll 接受 Number 的超类型
List<Object> objects = new ArrayList<>();
numberStack.popAll(objects);

System.out.println(objects);  // [3, 2.0, 1]

3.3 类型安全异构容器实战:插件系统

java
public class PluginRegistry {
    private final Map<Class<?>, Object> plugins = new HashMap<>();

    public <T> void register(Class<T> type, T plugin) {
        plugins.put(Objects.requireNonNull(type), Objects.requireNonNull(plugin));
    }

    public <T> Optional<T> getPlugin(Class<T> type) {
        @SuppressWarnings("unchecked")
        T plugin = (T) plugins.get(type);
        return Optional.ofNullable(plugin);
    }

    public <T> T getPluginOrThrow(Class<T> type) {
        return getPlugin(type)
            .orElseThrow(() -> new IllegalArgumentException(
                "No plugin registered for " + type.getName()));
    }

    public int size() {
        return plugins.size();
    }
}

// 插件接口
interface Logger {
    void log(String message);
}

interface MetricsCollector {
    void record(String metric, double value);
}

interface Cache {
    void put(String key, Object value);
    Object get(String key);
}

// 插件实现
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

class SimpleMetrics implements MetricsCollector {
    private final Map<String, List<Double>> metrics = new HashMap<>();

    @Override
    public void record(String metric, double value) {
        metrics.computeIfAbsent(metric, k -> new ArrayList<>()).add(value);
    }

    public double average(String metric) {
        return metrics.getOrDefault(metric, List.of(0.0))
            .stream().mapToDouble(Double::doubleValue).average().orElse(0);
    }
}

class MemoryCache implements Cache {
    private final Map<String, Object> store = new HashMap<>();

    @Override
    public void put(String key, Object value) {
        store.put(key, value);
    }

    @Override
    public Object get(String key) {
        return store.get(key);
    }
}

// 使用
PluginRegistry registry = new PluginRegistry();
registry.register(Logger.class, new ConsoleLogger());
registry.register(MetricsCollector.class, new SimpleMetrics());
registry.register(Cache.class, new MemoryCache());

Logger logger = registry.getPluginOrThrow(Logger.class);
logger.log("Application started");

MetricsCollector metrics = registry.getPluginOrThrow(MetricsCollector.class);
metrics.record("response_time", 150.5);
metrics.record("response_time", 200.3);

Cache cache = registry.getPluginOrThrow(Cache.class);
cache.put("user:1", "Alice");

四、本章小结

第3章通过8条经验法则,系统讲解了 Java 泛型编程的核心知识:

消除非受检警告:每个 unchecked 警告都应该被消除或证明是安全的。

列表优于数组:数组是协变的且可具体化的,泛型是不可变的且被擦除的,两者不兼容。

优先考虑泛型:泛型类和方法提供了编译时类型安全。

PECS 原则:生产者用 extends,消费者用 super。

类型安全的异构容器:使用 Class<?> 作为 key,可以在一个容器中安全地存储多种类型的对象。

泛型是 Java 类型系统的核心扩展。理解类型擦除的本质,掌握通配符的使用,才能写出既灵活又安全的泛型代码。记住:泛型的目的是让错误在编译时被发现,而不是在运行时。


五、思考与练习

思考题

为什么 Java 的泛型使用类型擦除而不是具体化?这带来了哪些限制?

List<? extends Number> 和 List<? super Integer> 有什么区别?分别在什么场景下使用?

如何在不使用泛型数组的情况下实现泛型集合?

类型令牌(type token)的局限性是什么?如何克服?

为什么 Map<Class<?>, Object> 不是类型安全的?如何通过泛型方法使其安全?

编程练习

实现泛型 Pair 类:支持 first() 和 second() 方法,实现 equals、hashCode 和 toString。

实现泛型 Result 类:类似 Rust 的 Result<T, E>,支持 ok(T) 和 err(E) 两种状态,提供 map、flatMap 和 orElse 方法。

实现类型安全的 Event Bus:使用异构容器存储不同类型的事件监听器,支持发布和订阅事件。


3/10泛型
0%