1. 概述

在使用Java编程时,我们有时需要根据给定的对象列表生成另一个类型不同的列表。Java 8引入了一系列新特性,简化了这类操作。

本教程将介绍如何利用Java 8及以后版本的强大功能,基于给定的员工对象列表创建一个不同类型的网球选手候选人列表。

2. 问题介绍

如常,让我们通过实例来理解这个问题。

假设一家公司要举办内部网球比赛,赛委会需要从所有员工中筛选出参赛者候选人。我们将构建一个程序来构建这个候选人名单。

Employee 类已经准备好了:

@Getter
class Employee {
    private final String name;
    private final Set<String> hobbies = new HashSet<>();
    private final String email;
    private String department;
    // ... other attributes

    public Employee(String name, String email, Collection<String> hobbies) {
        this.name = name;
        this.email = email;
        this.hobbies.addAll(hobbies);
    }
}

代码中使用了Lombok的注解 @Getter,使Employee 类具有所有属性的getter方法。

每个Employee对象都有一个名为hobbiesSet,用于存储员工的爱好,以字符串形式表示。我们的任务是遍历员工,如果某员工的爱好包含“网球”,则认为他们是网球选手的候选人。最终,我们将得到一个TennisPlayerCandidate 实例的列表:

class TennisPlayerCandidate {
    private final String name;
    private final String email;
    private final Boolean confirmed = Boolean.FALSE;
    public TennisPlayerCandidate(String name, String email) {
        this.name = name;
        this.email = email;
    }
    
  // equals() and hashcode() methods are omitted
}

作为输入,假设EMPLOYEES 列表包含五个对象:

final static List<Employee> EMPLOYEES = Lists.newArrayList(
  new Employee("Kai", "[email protected]", Lists.newArrayList("Football", "Reading", "Chess")),
  new Employee("Eric", "[email protected]", Lists.newArrayList("Tennis", "Baseball", "Singing")),
  new Employee("Saajan", "[email protected]", Lists.newArrayList("Tennis", "Baseball", "Reading")),
  new Employee("Kevin", "[email protected]", Lists.newArrayList("Dancing", "Computer Games", "Tennis")),
  new Employee("Amanda", "[email protected]", Lists.newArrayList("Painting", "Yoga", "Dancing"))
);

目标是根据这个输入,得到如下TennisPlayerCandidate 列表:

final static List<TennisPlayerCandidate> EXPECTED = Lists.newArrayList(
  new TennisPlayerCandidate("Eric", "[email protected]"),
  new TennisPlayerCandidate("Saajan", "[email protected]"),
  new TennisPlayerCandidate("Kevin", "[email protected]")
);

接下来,我们将探讨不同的方法,从给定的Employee 列表构建预期的TennisPlayerCandidate 列表。

为了简化,我们将使用单元测试断言来验证每种方法是否能产生预期结果。

3. 使用List.forEach()方法

解决这个问题的一个直接方法是首先初始化一个空的候选人列表。然后,遍历EMPLOYEES 列表,对于列出“网球”爱好的每个员工,创建一个TennisPlayerCandidate 对象,并将其添加到候选人列表中,如果满足这个条件。

Java 8引入了forEach()方法,它允许我们在遍历列表时方便地执行操作:

List<TennisPlayerCandidate> result = new ArrayList<>();
EMPLOYEES.forEach(e -> {
    if (e.getHobbies().contains("Tennis")) {
        result.add(new TennisPlayerCandidate(e.getName(), e.getEmail()));
    }
});
assertEquals(EXPECTED, result);

正如我们所见,这种方法可以有效地完成任务。

除了forEach()方法,自Java 8以来,Stream API彻底改变了我们处理和转换数据集合的方式。

接下来,我们将使用Stream API来解决问题。

4. 使用Stream.map()Collectors.mapping()

我们可以这样解读问题:过滤出爱好包含网球的员工,并将这些Employee对象转换为TennisPlayerCandidate对象。

Stream的filter()map()方法可以帮助我们轻松完成任务。现在,让我们将这个想法转化为Java代码:

List<TennisPlayerCandidate> result = EMPLOYEES.stream()
  .filter(e -> e.getHobbies().contains("Tennis"))
  .map(e -> new TennisPlayerCandidate(e.getName(), e.getEmail()))
  .collect(Collectors.toList());
assertEquals(EXPECTED, result);

如代码所示,无需预先准备TennisPlayerCandidate对象的列表。**filter().map()管道会返回一个TennisPlayerCandidate实例的Stream。我们需要做的只是将对象收集到一个列表中。**

另一种选择是将映射逻辑移动到收集阶段。换句话说,在收集过滤后的Employee实例时,我们将它们转换为TennisPlayerCandidate对象

Collectors.mapping()方法允许我们在Stream上执行对象转换和收集操作:

List<TennisPlayerCandidate> result = EMPLOYEES.stream()
  .filter(e -> e.getHobbies().contains("Tennis"))
  .collect(Collectors.mapping(e -> new TennisPlayerCandidate(e.getName(), e.getEmail()), Collectors.toList()));
assertEquals(EXPECTED, result);

5. 总结

在这篇文章中,我们探讨了三种根据给定列表创建不同类型对象列表的方法。通过实例,我们了解到在Java中使用Stream API处理列表时,可以提高代码的生产力和可读性。

如往常一样,示例的完整源代码可在GitHub上找到。