使用收集器重构Java 8代码


我打算使用功能组件(特别是Collector)将基于Java 7的代码重构为Java 8。该计划很简单:通过编码来学习它。让我们开始吧…

我们正在处理的Java对象概述 java8-fun.png 重构的Java对象

以下代码旨在从Company对象(该公司具有员工列表)中获取一些特定的数据。我要执行以下操作:

  • 找出公司中最常用的姓名(员工姓名)。
  • 寻找公司最高薪水的雇员。
  • 找到所有男人的薪水总和。
  • 查找公司中最受欢迎的等级/乐队。

在Java 8之前,很难重构此逻辑并找出一些通用部分。但是功能组件使重构变得容易。

Java 8之前的版本 让我们看一下代码,

找出公司中最普通的员工姓名

private static String findMostRepeatingNameInCompany(Company company) {
    String repeatingName = null;
    Map<String, Integer> nameAndCount = new HashMap<>();
    for (Department department : company.getDepartments()) {
        for (Employee employee : department.getEmployees()) {
            if (!nameAndCount.containsKey(employee.getName())) {
                nameAndCount.put(employee.getName(), 1);
            } else {
                Integer count = nameAndCount.get(employee.getName());
                count++;
                nameAndCount.put(employee.getName(), count);
            }
        }
    }
    for (Map.Entry<String, Integer> entry : nameAndCount.entrySet()) {
        if (entry.getValue().equals(Collections.max(nameAndCount.values()))) {
            repeatingName = entry.getKey();
        }
    }
    return repeatingName;
}

寻找薪水最高的员工

private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
    Employee costlyEmployee = null;
    Map<Employee, Long> employeeAndSalary = new HashMap<>();
    for (Department department : company.getDepartments()) {
        for (Employee employee : department.getEmployees()) {
            employeeAndSalary.put(employee, employee.getSalary());
        }
    }
    for (Map.Entry<Employee, Long> entry : employeeAndSalary.entrySet()) {
        if (entry.getValue().equals(Collections.max(employeeAndSalary.values()))) {
            costlyEmployee = entry.getKey();
        }
    }
    return costlyEmployee;
}

找到所有男人的薪水总和

private static Long findSumOfAllMenSalary(Company company) {
    Long totalSalary = 0L;
    for (Department department : company.getDepartments()) {
        for (Employee employee : department.getEmployees()) {
            if (employee.getGender().equals(Gender.MALE)) {
                totalSalary = totalSalary + employee.getSalary();
            }
        }
    }
    return totalSalary;
}

查找公司中最受欢迎的等级/乐队

private static Band findMostPopularBandInCompany(Company company) {
    Band popularBand = null;
    Map<Band, Integer> bandAndCount = new HashMap<>();
    for (Department department : company.getDepartments()) {
        for (Employee employee : department.getEmployees()) {
            if (!bandAndCount.containsKey(employee.getBand())) {
                bandAndCount.put(employee.getBand(), 1);
            } else {
                Integer count = bandAndCount.get(employee.getBand());
                count++;
                bandAndCount.put(employee.getBand(), count);
            }
        }
    }
    for (Map.Entry<Band, Integer> entry : bandAndCount.entrySet()) {
        if (entry.getValue().equals(Collections.max(bandAndCount.values()))) {
            popularBand = entry.getKey();
        }
    }
    return popularBand;
}

在所有情况下,我们都需要遍历各个部门,然后遍历员工。但是,由于我们要收集的条件和数据的格式不同,因此我们将无法找到常见的东西并对其进行适当的重构。让我们看看Java 8是如何处理的。

首先,让我们使用流并进行处理。

找出公司中最普通的员工姓名 让我们看一下的逻辑nameAndCount,因为迭代(部门和员工上的for循环)已定义result(nameAndCount)。我们可以使用流来获取该值:

company.getDepartments().stream().flatMap(department -> department.getEmployees().stream())
 .forEach(employee -> addToNameAndCountMap(nameAndCount, employee));

...
...

private static void addToNameAndCountMap(Map<String, Integer> nameAndCount, Employee employee) {
    if (!nameAndCount.containsKey(employee.getName())) {
        nameAndCount.put(employee.getName(), 1);
    } else {
        Integer count = nameAndCount.get(employee.getName());
        count++;
        nameAndCount.put(employee.getName(), count);
    }
}

为什么我们没有通过收集获得结果?我们在那里有一些逻辑。我们需要识别重复项。地图的值是员工姓名的数量。让我们看看最终结果。

private static String findMostRepeatingNameInCompany(Company company) {
    Map<String, Integer> nameAndCount = new HashMap<>();
    company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .forEach(employee -> addToNameAndCountMap(nameAndCount, employee));
    String repeatingName = null;
    Integer maxCount = Collections.max(nameAndCount.values());
    Optional<String> repeat = nameAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxCount))
            .map(Map.Entry::getKey).findFirst();
    if (repeat.isPresent()) {
        repeatingName = repeat.get();
    }
    return repeatingName;
}

private static void addToNameAndCountMap(Map<String, Integer> nameAndCount, Employee employee) {
    if (!nameAndCount.containsKey(employee.getName())) {
        nameAndCount.put(employee.getName(), 1);
    } else {

        Integer count = nameAndCount.get(employee.getName());
        count++;
        nameAndCount.put(employee.getName(), count);
    }
}

寻找薪水最高的员工 让我们在这里也进行相同的重构。但是在这里,我们可以使用collect,因为逻辑相当简单。

private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
    Employee costlyEmployee = null;
    Map<Employee, Long> employeeAndSalary = company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .collect(Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b));
    Long maxSalary = Collections.max(employeeAndSalary.values());
    Optional<Employee> costly = employeeAndSalary.entrySet().stream()
            .filter(e -> e.getValue().equals(maxSalary)).map(Map.Entry::getKey).findFirst();
    if(costly.isPresent()) {
        costlyEmployee = costly.get();
    }
    return costlyEmployee;
}

找到所有男人的薪水总和 好吧,这似乎简单得多。

private static Long findSumOfAllMenSalary(Company company) {
    return company.getDepartments().stream()
            .flatMap(d -> d.getEmployees().stream())
            .filter(e -> e.getGender().equals(Gender.MALE))
            .map(Employee::getSalary).mapToLong(Long::longValue).sum();
}

查找公司中最受欢迎的等级/乐队 我们需要应用与第一种方法类似的逻辑。(我的意思是findMostRepeatingNameInCompany。)让我们尝试一下。

private static Band findMostPopularBandInCompany(Company company) {
    Map<Band, Integer> bandAndCount = new HashMap<>();
    company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .forEach(employee -> addToBandAndCoutMap(bandAndCount, employee));
    Band popularBand = null;
    Integer maxBand = Collections.max(bandAndCount.values());
    Optional<Band> popular = bandAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxBand))
            .map(Map.Entry::getKey).findFirst();
    if(popular.isPresent()) {
        popularBand = popular.get();
    }
    return popularBand;
}

private static void addToBandAndCoutMap(Map<Band, Integer> bandAndCount, Employee employee) {
    if (!bandAndCount.containsKey(employee.getBand())) {
        bandAndCount.put(employee.getBand(), 1);
    } else {
        Integer count = bandAndCount.get(employee.getBand());
        count++;
        bandAndCount.put(employee.getBand(), count);
    }
}

我们有一些共同的价值观,如果我们发现了那些共同的事物并适当地重构它,也许能够节省一些行,让我们一起去做吧……

这次,让我们stream.collect()到处使用方法。因此,我们仅对两个方法调用findMostRepeatingNameInCompany和进行了更改findMostPopularBandInCompany。

找出公司中最普通的员工姓名 让我们尝试在此处使用collect,并避免addToNameAndCountMap方法调用。如果使用正确的收集器,则可以使用自定义键或值将数据收集到地图。我们将为此使用Collectors.groupingBy(Employee::getName,Collectors.counting())

private static String findMostRepeatingNameInCompany(Company company) {
    Map<String, Long> nameAndCount = company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .collect(Collectors.groupingBy(Employee::getName,Collectors.counting()));
    Long maxCount = Collections.max(nameAndCount.values());
    Optional<String> repeat = nameAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxCount))
            .map(Map.Entry::getKey).findFirst();
    String repeatingName = null;
    if (repeat.isPresent()) {
        repeatingName = repeat.get();
    }
    return repeatingName;
}

查找公司中最受欢迎的等级/乐队 让我们在这里也做同样的事情。

private static Band findMostPopularBandInCompany(Company company) {
    Map<Band, Long> bandAndCount = company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .collect(Collectors.groupingBy(Employee::getBand, Collectors.counting()));
    Band popularBand = null;
    Long maxBand = Collections.max(bandAndCount.values());
    Optional<Band> popular = bandAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxBand))
            .map(Map.Entry::getKey).findFirst();
    if(popular.isPresent()) {
        popularBand = popular.get();
    }
    return popularBand;
}

现在我们有了一个通用的模式,似乎有可能从中提取一些功能组件。让我们探讨这种可能性。

我们可以看到,方法findMostRepeatingNameInCompany,findEmployeeWithHighestSalaryInTheCompany并findMostPopularBandInCompany遵循类似的模式。唯一的区别是地图的类型和我们使用的收集器。因此,让我们编写一个返回通用映射并接受收集器的方法。

private static <T> Map<T, Long> processEmployeeToMap(Company company,
                                                     Collector<Employee, ?, Map<T,Long>>
                                                     employeeMapCollector) {
    return company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .collect(employeeMapCollector);
}

这是一种通用方法,适用于不同的输入,如果T是字符串,它将使用收集器获取aMap<String, Long>作为返回类型。如果我们使用另一个对象(例如Employee),则返回类型将为Map<Employee, Long>。让我们尝试在接下来的三个方法中使用此方法调用。

找出公司中最普通的员工姓名

private static String findMostRepeatingNameInCompany(Company company) {
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
            Collectors.groupingBy(Employee::getName,Collectors.counting());
    Map<String, Long> nameAndCount = processEmployeeToMap(company,repeatingNameCollector);
    Long maxCount = Collections.max(nameAndCount.values());
    Optional<String> repeat = nameAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxCount))
            .map(Map.Entry::getKey).findFirst();
    String repeatingName = null;
    if (repeat.isPresent()) {
        repeatingName = repeat.get();
    }
    return repeatingName;
}

寻找薪水最高的员工 让我们在这里也进行相同的重构:

private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
    Collector<Employee,?,Map<Employee,Long>> highSalary =
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
    Map<Employee, Long> employeeAndSalary = processEmployeeToMap(company,highSalary);
    Long maxSalary = Collections.max(employeeAndSalary.values());
    Optional<Employee> costly = employeeAndSalary.entrySet().stream()
            .filter(e -> e.getValue().equals(maxSalary)).map(Map.Entry::getKey).findFirst();
    Employee costlyEmployee = null;
    if(costly.isPresent()) {
        costlyEmployee = costly.get();
    }
    return costlyEmployee;
}

查找公司中最受欢迎的等级/乐队

private static Band findMostPopularBandInCompany(Company company) {
    Collector<Employee,?,Map<Band,Long>> popularBandCollector =
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
    Map<Band, Long> bandAndCount = processEmployeeToMap(company,popularBandCollector);
    Band popularBand = null;
    Long maxBand = Collections.max(bandAndCount.values());
    Optional<Band> popular = bandAndCount.entrySet().stream()
            .filter(e -> e.getValue().equals(maxBand))
            .map(Map.Entry::getKey).findFirst();
    if(popular.isPresent()) {
        popularBand = popular.get();
    }
    return popularBand;
}

找到所有男人的薪水总和 好吧,我们这里需要一种不同的方式,因为这种方式与其余方法完全不同。

public static Function<Department, Stream<Employee>> 
          allEmployeeInDept = department -> department.getEmployees().stream();

private static Long findSumOfAllMenSalary(Company company) {
    return company.getDepartments().stream()
            .flatMap(d -> allEmployeeInDept.apply(d))
            .filter(e -> e.getGender().equals(Gender.MALE))
            .map(Employee::getSalary).mapToLong(Long::longValue).sum();
}

Well, we have used the functional components and achieved a better reusable code, but still, this can be cleaned up a bit.

You might have noticed that all these methods need a single value in return, not a list. And, we use optional and some logic to identify that single element. Can we extract that too, let’s try that now.

private static <T> T fetchParamsFromMap(Map<T, Long> param) {
    return param.entrySet().stream()
            .filter(e -> e.getValue().equals(Collections.max(param.values())))
            .map(Map.Entry::getKey).findFirst().orElse(null);
}

This method returns the key of the biggest param in a map, using generics, this one can be applied to all of our methods

Find out the most common employee name in the company

private static String findMostRepeatingNameInCompany(Company company) {
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
            Collectors.groupingBy(Employee::getName, Collectors.counting());
    Map<String, Long> nameAndCount = processEmployeeToMap(company, repeatingNameCollector);
    return fetchParamsFromMap(nameAndCount);
}

that simple? yes, it is

Find the highest salaried employee

private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
    Collector<Employee, ?, Map<Employee, Long>> highSalary =
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
    Map<Employee, Long> employeeAndSalary = processEmployeeToMap(company, highSalary);
    return fetchParamsFromMap(employeeAndSalary);
}

Find the most popular grade/band in the company

private static Band findMostPopularBandInCompany(Company company) {
    Collector<Employee, ?, Map<Band, Long>> popularBandCollector =
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
    Map<Band, Long> bandAndCount = processEmployeeToMap(company, popularBandCollector);
    return fetchParamsFromMap(bandAndCount);
}

完毕?不,我们还没有完成……还有很多事情要做。

在这些方法中findMostRepeatingNameInCompany,findEmployeeWithHighestSalaryInTheCompanyfindMostPopularBandInCompany,仍然是一个重复的模式,我们用processEmployeeToMap了,马上,我们叫fetchParamsFromMap。让我们结合起来。

private static <T> T fetchBestOfMappedEmployees(Company company,
                                                Collector<Employee, ?, Map<T, Long>> employeeMapCollector) {
    Map<T, Long> mappedResults = company.getDepartments().stream()
            .flatMap(department -> department.getEmployees().stream())
            .collect(employeeMapCollector);
    return mappedResults.entrySet().stream()
            .filter(e -> e.getValue().equals(Collections.max(mappedResults.values())))
            .map(Map.Entry::getKey).findFirst().orElse(null);
}

现在,让我们看看我们的will方法的外观:

找出公司中最普通的员工姓名

private static String findMostRepeatingNameInCompany(Company company) {
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
            Collectors.groupingBy(Employee::getName, Collectors.counting());
    return fetchBestOfMappedEmployees(company, repeatingNameCollector);
}

寻找薪水最高的员工

private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
    Collector<Employee, ?, Map<Employee, Long>> highSalary =
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
    return fetchBestOfMappedEmployees(company, highSalary);
}

查找公司中最受欢迎的等级/乐队

private static Band findMostPopularBandInCompany(Company company) {
    Collector<Employee, ?, Map<Band, Long>> popularBandCollector =
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
    return fetchBestOfMappedEmployees(company, popularBandCollector);
}

我们甚至可以继续简化此过程,可以将收集器放到外面,父调用可以传递该集合,然后我们就不需要此方法本身了。


原文链接:http://codingdict.com