1. 概述

本文将探讨在处理集合时如何使用 Java 的泛型(Generics)。我们将讨论extendssuper关键字,并通过一些PECS(生产者扩展消费者超集)规则的例子来理解如何正确运用这些关键字。

2. 生产者扩展(Producer Extends)

本文中的代码示例将基于一个简单的数据模型,包含一个基础类User和两个扩展类:OperatorCustomer

重要的是要明白,PECS 规则必须从集合的角度来应用。换句话说,如果我们遍历一个List并处理其元素,那么列表将作为我们逻辑的生产者:

public void sendEmails(List<User> users) {
    for (User user : users) {
        System.out.println("sending email to " + user);
    }
}

假设我们想在List<Operator>上使用sendEmail方法。由于Operator类继承自User,我们可能会期望这只是一个直接的方法调用。但不幸的是,这会导致编译错误:

生产者

为了解决问题,我们可以按照PECS规则更新sendEmail方法。因为用户列表是逻辑的生产者,所以我们使用extends关键字:

public void sendEmailsFixed(List<? extends User> users) {
    for (User user : users) {
        System.out.println("sending email to " + user);
    }
}

因此,现在我们可以轻松地对任何继承自User类的泛型类型列表调用此方法:

List<Operator> operators = Arrays.asList(new Operator("sam"), new Operator("daniel"));
List<Customer> customers = Arrays.asList(new Customer("john"), new Customer("arys"));

sendEmailsFixed(operators);
sendEmailsFixed(customers);

3. 消费者超集(Consumer Supers)

当我们向集合中添加元素时,我们成为生产者,而列表则作为消费者。让我们编写一个方法,它接收一个Operator列表并向其中添加两个更多元素:

private void addUsersFromMarketingDepartment(List<Operator> users) {
    users.add(new Operator("john doe"));
    users.add(new Operator("jane doe"));
}

如果传递一个Operator列表,这将完美工作。但如果我们要将这两个操作员添加到一个Users列表中,又会遇到编译错误:消费者超集错误

因此,我们需要更新方法,使其接受Operator或其前辈类型的集合,使用super关键字:

private void addUsersFromMarketingDepartmentFixed(List<? super Operator> users) {
    users.add(new Operator("john doe"));
    users.add(new Operator("jane doe"));
}

4. 生产与消费

有时,我们的逻辑可能需要同时读取和写入集合。在这种情况下,集合既是生产者也是消费者。

处理这种场景的唯一方法是使用基类,不使用任何关键字:

private void addUsersAndSendEmails(List<User> users) {
    users.add(new Operator("john doe"));
    for (User user : users) {
        System.out.println("sending email to: " + user);
    }
}

然而,如果同一个集合用于读取和写入,这将违反命令查询分离原则,应避免。

5. 总结

在这篇文章中,我们讨论了PECS规则,并学习了如何在处理Java集合时应用它。我们探索了各种情况下集合作为我们逻辑的生产者或消费者的情况。然后,我们了解到,如果集合同时进行读写操作,可能表明我们的设计存在代码异味。

本文所有示例代码可在GitHub上找到。