Java8学习笔记(七)--Collectors
你已经学习了Stream API能够让你以声明式的方式帮助你处理集合。我们看到collect是一个将管道流的结果集到一个list中的结束操作。collect是一个将数据流缩减为一个值的归约操作。这个值可以是集合、映射,或者一个值对象。你可以使用collect达到以下目的:
-
将数据流缩减为一个单一值:一个流执行后的结果能够被缩减为一个单一的值。单一的值可以是一个Collection,或者像int、double等的数值,再或者是一个用户自定义的值对象。
-
将一个数据流中的元素进行分组:根据任务类型将流中所有的任务进行分组。这将产生一个Map<TaskType, List
-
分割一个流中的元素:你可以将一个流分割为两组——比如将任务分割为要做和已经做完的任务。
Collector实际应用
为了感受到Collector的威力,让我们来看一下我们要根据任务类型来对任务进行分类的例子。在Java8中,我们可以通过编写如下的代码达到将任务根据类型分组的目的。
private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) { return tasks.stream().collect(Collectors.groupingBy(task -> task.getType())); }
上面的代码使用了定义在辅助类Collectors中的groupingBy收集器。它创建了一个映射,其中TaskType是它的键,而包含了所有拥有相同TaskType的任务的列表是它的值。为了在Java7中达到相同的效果,你需要编写如下的代码。
public static void main(String[] args) { List<Task> tasks = getTasks(); Map<TaskType, List<Task>> allTasksByType = new HashMap<>(); for (Task task : tasks) { List<Task> existingTasksByType = allTasksByType.get(task.getType()); if (existingTasksByType == null) { List<Task> tasksByType = new ArrayList<>(); tasksByType.add(task); allTasksByType.put(task.getType(), tasksByType); } else { existingTasksByType.add(task); } } for (Map.Entry<TaskType, List<Task>> entry : allTasksByType.entrySet()) { System.out.println(String.format("%s =>> %s", entry.getKey(), entry.getValue())); } }
收集器:常用的规约操作
Collectors辅助类提供了大量的静态辅助方法来创建收集器为常见的使用场景服务,像将元素收集到一个集合中、分组和分割元素,或者根据不同的标准来概述元素。我们将在这篇博文中涵盖大部分常见的Collector。
-
将数据收集进一个列表
import static java.util.stream.Collectors.toList; public class Example2_ReduceValue { public List<String> allTitles(List<Task> tasks) { return tasks.stream().map(Task::getTitle).collect(toList()); } }
-
将数据收集进一个集合
import static java.util.stream.Collectors.toSet; public Set<String> uniqueTitles(List<Task> tasks) { return tasks.stream().map(Task::getTitle).collect(toSet()); }
-
将数据收集进一个映射
private static Map<String, Task> taskMap(List<Task> tasks) { return tasks.stream().collect(toMap(Task::getTitle, task -> task)); }
我们可以通过使用Function接口中的默认方法identity来改进上面展示的代码,如下所示,这样可以让代码更加简洁,并更好地传达开发者的意图。import static java.util.function.Function.identity; private static Map<String, Task> taskMap(List<Task> tasks) { return tasks.stream().collect(toMap(Task::getTitle, identity())); }
-
使用其它的收集器
像toList和toSet这类特定的收集器不允许你指定内部的列表或者集合实现。当你想要将结果收集到其它类型的集合中时,你可以像下面这样使用toCollection收集器。private static LinkedHashSet<Task> collectToLinkedHaskSet(List<Task> tasks) { return tasks.stream().collect(toCollection(LinkedHashSet::new)); }
找到拥有最长标题的任务
public Task taskWithLongestTitle(List<Task> tasks) { return tasks.stream().collect(collectingAndThen(maxBy((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()), Optional::get)); }
统计标签的总数
public int totalTagCount(List<Task> tasks) { return tasks.stream().collect(summingInt(task -> task.getTags().size())); }
生成任务标题的概述
public String titleSummary(List<Task> tasks) { return tasks.stream().map(Task::getTitle).collect(joining(";")); }
-
分类收集器
收集器最常见的使用场景之一是对元素进行分类。让我来看一下不同的例子来理解我们如何进行分类。根据类型对任务分类
import static java.util.stream.Collectors.groupingBy; private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) { return tasks.stream().collect(groupingBy(Task::getType)); }
根据标签分类
private static Map<String, List<Task>> groupingByTag(List<Task> tasks) { return tasks.stream(). flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))). collect(groupingBy(TaskTag::getTag, mapping(TaskTag::getTask,toList()))); } private static class TaskTag { final String tag; final Task task; public TaskTag(String tag, Task task) { this.tag = tag; this.task = task; } public String getTag() { return tag; } public Task getTask() { return task; } }
private static Map<String, Long> tagsAndCount(List<Task> tasks) { return tasks.stream(). flatMap(task -> task.getTags().stream().map(tag -> new TaskTag(tag, task))). collect(groupingBy(TaskTag::getTag, counting())); }
根据任务类型和创建日期分类
private static Map<TaskType, Map<LocalDate, List<Task>>> groupTasksByTypeAndCreationDate(List<Task> tasks) { return tasks.stream().collect(groupingBy(Task::getType, groupingBy(Task::getCreatedOn))); }
-
分割收集器
很多时候你想根据一个断言来将一个数据集分割成两个数据集。举例来说,我们可以通过定义一个将任务分割为两组的分割方法来将任务分割成两组,一组是在今天之前已经到期的,另一组是其他的任务。
private static Map<Boolean, List<Task>> partitionOldAndFutureTasks(List<Task> tasks) { return tasks.stream().collect(partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now()))); }
-
生成统计信息
另一组非常有用的收集器是用来产生统计信息的收集器。这能够在像int、double和long这样的原始数据类型上起到作用;并且能被用来生成像下面这样的统计信息。IntSummaryStatistics summaryStatistics = tasks.stream().map(Task::getTitle).collect(summarizingInt(String::length)); System.out.println(summaryStatistics.getAverage()); //32.4 System.out.println(summaryStatistics.getCount()); //5 System.out.println(summaryStatistics.getMax()); //44 System.out.println(summaryStatistics.getMin()); //24 System.out.println(summaryStatistics.getSum()); //162
你也可以通过使用combine操作来将一个IntSummaryStatistics与另一个组合起来。
firstSummaryStatistics.combine(secondSummaryStatistics); System.out.println(firstSummaryStatistics)
-
Java8中的字数统计
public static void wordCount(Path path) throws IOException { Map<String, Long> wordCount = Files.lines(path) .parallel() .flatMap(line -> Arrays.stream(line.trim().split("\\s"))) .map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase().trim()) .filter(word -> word.length() > 0) .map(word -> new SimpleEntry<>(word, 1)) .collect(groupingBy(SimpleEntry::getKey, counting())); wordCount.forEach((k, v) -> System.out.println(String.format("%s ==>> %d", k, v))); }
岁月本长而忙者自促;天地本宽而卑者自隘;风花雪月本闲,而劳忧者自冗;天行健,君子以自强不息;地势坤,君子以厚德载物;宠辱不惊,闲看庭前花开花落;去留无意,漫随天外云卷云舒.不妄取,不妄予,不妄想,不妄求,与人方便,随遇而安