1. 概述
本文将探讨在处理集合时如何使用 Java 的泛型(Generics)。我们将讨论extends
和super
关键字,并通过一些PECS(生产者扩展消费者超集)规则的例子来理解如何正确运用这些关键字。
2. 生产者扩展(Producer Extends)
本文中的代码示例将基于一个简单的数据模型,包含一个基础类User
和两个扩展类:Operator
和Customer
。
重要的是要明白,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上找到。