Java8学习笔记(七)--Collectors
本系列文章翻譯自@shekhargulati的java8-the-missing-tutorial
你已經(jīng)學(xué)習(xí)了Stream API能夠讓你以聲明式的方式幫助你處理集合。我們看到collect是一個(gè)將管道流的結(jié)果集到一個(gè)list中的結(jié)束操作。collect是一個(gè)將數(shù)據(jù)流縮減為一個(gè)值的歸約操作。這個(gè)值可以是集合、映射,或者一個(gè)值對象。你可以使用collect達(dá)到以下目的:
將數(shù)據(jù)流縮減為一個(gè)單一值:一個(gè)流執(zhí)行后的結(jié)果能夠被縮減為一個(gè)單一的值。單一的值可以是一個(gè)Collection,或者像int、double等的數(shù)值,再或者是一個(gè)用戶自定義的值對象。
將一個(gè)數(shù)據(jù)流中的元素進(jìn)行分組:根據(jù)任務(wù)類型將流中所有的任務(wù)進(jìn)行分組。這將產(chǎn)生一個(gè)Map<TaskType, List
分割一個(gè)流中的元素:你可以將一個(gè)流分割為兩組——比如將任務(wù)分割為要做和已經(jīng)做完的任務(wù)。
Collector實(shí)際應(yīng)用
為了感受到Collector的威力,讓我們來看一下我們要根據(jù)任務(wù)類型來對任務(wù)進(jìn)行分類的例子。在Java8中,我們可以通過編寫如下的代碼達(dá)到將任務(wù)根據(jù)類型分組的目的。
private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {return tasks.stream().collect(Collectors.groupingBy(task -> task.getType())); }上面的代碼使用了定義在輔助類Collectors中的groupingBy收集器。它創(chuàng)建了一個(gè)映射,其中TaskType是它的鍵,而包含了所有擁有相同TaskType的任務(wù)的列表是它的值。為了在Java7中達(dá)到相同的效果,你需要編寫如下的代碼。
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()));} }收集器:常用的規(guī)約操作
Collectors輔助類提供了大量的靜態(tài)輔助方法來創(chuàng)建收集器為常見的使用場景服務(wù),像將元素收集到一個(gè)集合中、分組和分割元素,或者根據(jù)不同的標(biāo)準(zhǔn)來概述元素。我們將在這篇博文中涵蓋大部分常見的Collector。
縮減為一個(gè)值
正如上面討論的,收集器可以被用來收集流的輸出到一個(gè)集合,或者產(chǎn)生一個(gè)單一的值。
將數(shù)據(jù)收集進(jìn)一個(gè)列表
讓我們編寫我們的第一個(gè)測試用例——給定一個(gè)任務(wù)列表,我們想將他們的標(biāo)題收集進(jìn)一個(gè)列表。
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());} }toList收集器使用了列表的add方法來向結(jié)果列表中添加元素。toList收集器使用了ArrayList作為列表的實(shí)現(xiàn)。
將數(shù)據(jù)收集進(jìn)一個(gè)集合
如果我們想要確保返回的標(biāo)題都是唯一的,并且我們不在乎元素的順序,那么我們可以使用toSet收集器。
import static java.util.stream.Collectors.toSet;public Set<String> uniqueTitles(List<Task> tasks) {return tasks.stream().map(Task::getTitle).collect(toSet()); }toSet方法使用了HashSet作為集合的實(shí)現(xiàn)來存儲結(jié)果集。
將數(shù)據(jù)收集進(jìn)一個(gè)映射
你可以使用toMap收集器將一個(gè)流轉(zhuǎn)換為一個(gè)映射。toMap收集器需要兩個(gè)映射方法來獲得映射的鍵和值。在下面展示的代碼中,Task::getTitle是接收一個(gè)任務(wù)并產(chǎn)生一個(gè)只包含該任務(wù)標(biāo)題的鍵的Function。task -> task是一個(gè)用來返回任務(wù)本身的lambda表達(dá)式。
private static Map<String, Task> taskMap(List<Task> tasks) {return tasks.stream().collect(toMap(Task::getTitle, task -> task)); }我們可以通過使用Function接口中的默認(rèn)方法identity來改進(jìn)上面展示的代碼,如下所示,這樣可以讓代碼更加簡潔,并更好地傳達(dá)開發(fā)者的意圖。
import static java.util.function.Function.identity;private static Map<String, Task> taskMap(List<Task> tasks) {return tasks.stream().collect(toMap(Task::getTitle, identity())); }從一個(gè)流中創(chuàng)建映射的代碼會在存在重復(fù)的鍵時(shí)拋出異常。你將會得到一個(gè)類似下面的錯(cuò)誤。
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Task{title='Read Version Control with Git book', type=READING} at java.util.stream.Collectors.lambda$throwingMerger$105(Collectors.java:133)你可以通過使用toMap方法的另一個(gè)變體來處理重復(fù)問題,它允許我們指定一個(gè)合并方法。這個(gè)合并方法允許用戶他們指定想如何處理多個(gè)值關(guān)聯(lián)到同一個(gè)鍵的沖突。在下面展示的代碼中,我們只是使用了新的值,當(dāng)然你也可以編寫一個(gè)智能的算法來處理沖突。
private static Map<String, Task> taskMap_duplicates(List<Task> tasks) {return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2)); }你可以通過使用toMap方法的第三個(gè)變體來指定其他的映射實(shí)現(xiàn)。這需要你指定將用來存儲結(jié)果的Map和Supplier。
public Map<String, Task> collectToMap(List<Task> tasks) {return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2, LinkedHashMap::new)); }類似于toMap收集器,也有toConcurrentMap收集器,它產(chǎn)生一個(gè)ConcurrentMap而不是HashMap。
使用其它的收集器
像toList和toSet這類特定的收集器不允許你指定內(nèi)部的列表或者集合實(shí)現(xiàn)。當(dāng)你想要將結(jié)果收集到其它類型的集合中時(shí),你可以像下面這樣使用toCollection收集器。
private static LinkedHashSet<Task> collectToLinkedHaskSet(List<Task> tasks) {return tasks.stream().collect(toCollection(LinkedHashSet::new)); }找到擁有最長標(biāo)題的任務(wù)
public Task taskWithLongestTitle(List<Task> tasks) {return tasks.stream().collect(collectingAndThen(maxBy((t1, t2) -> t1.getTitle().length() - t2.getTitle().length()), Optional::get)); }統(tǒng)計(jì)標(biāo)簽的總數(shù)
public int totalTagCount(List<Task> tasks) {return tasks.stream().collect(summingInt(task -> task.getTags().size())); }生成任務(wù)標(biāo)題的概述
public String titleSummary(List<Task> tasks) {return tasks.stream().map(Task::getTitle).collect(joining(";")); }分類收集器
收集器最常見的使用場景之一是對元素進(jìn)行分類。讓我來看一下不同的例子來理解我們?nèi)绾芜M(jìn)行分類。
例子1:根據(jù)類型對任務(wù)分類
我們看一下下面展示的例子,我們想要根據(jù)TaskType來對所有的任務(wù)進(jìn)行分類。我們可以通過使用Collectors輔助類中的groupingBy方法來輕易地進(jìn)行該項(xiàng)任務(wù)。你可以通過使用方法引用和靜態(tài)導(dǎo)入來使它更加高效。
import static java.util.stream.Collectors.groupingBy; private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {return tasks.stream().collect(groupingBy(Task::getType)); }它將會產(chǎn)生如下的輸出。
{CODING=[Task{title='Write a mobile application to store my tasks', type=CODING, createdOn=2015-07-03}], WRITING=[Task{title='Write a blog on Java 8 Streams', type=WRITING, createdOn=2015-07-04}], READING=[Task{title='Read Version Control with Git book', type=READING, createdOn=2015-07-01}, Task{title='Read Java 8 Lambdas book', type=READING, createdOn=2015-07-02}, Task{title='Read Domain Driven Design book', type=READING, createdOn=2015-07-05}]}
例子2:根據(jù)標(biāo)簽分類
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;}}例子3:根據(jù)標(biāo)簽和數(shù)量對任務(wù)分類
將分類器和收集器結(jié)合起來。
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())); }例子4:根據(jù)任務(wù)類型和創(chuàng)建日期分類
private static Map<TaskType, Map<LocalDate, List<Task>>> groupTasksByTypeAndCreationDate(List<Task> tasks) {return tasks.stream().collect(groupingBy(Task::getType, groupingBy(Task::getCreatedOn))); }分割
很多時(shí)候你想根據(jù)一個(gè)斷言來將一個(gè)數(shù)據(jù)集分割成兩個(gè)數(shù)據(jù)集。舉例來說,我們可以通過定義一個(gè)將任務(wù)分割為兩組的分割方法來將任務(wù)分割成兩組,一組是在今天之前已經(jīng)到期的,另一組是其他的任務(wù)。
private static Map<Boolean, List<Task>> partitionOldAndFutureTasks(List<Task> tasks) {return tasks.stream().collect(partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now()))); }生成統(tǒng)計(jì)信息
另一組非常有用的收集器是用來產(chǎn)生統(tǒng)計(jì)信息的收集器。這能夠在像int、double和long這樣的原始數(shù)據(jù)類型上起到作用;并且能被用來生成像下面這樣的統(tǒng)計(jì)信息。
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也有其它的變種形式,像針對其它原生類型的LongSummaryStatistics和DoubleSummaryStatistics。
你也可以通過使用combine操作來將一個(gè)IntSummaryStatistics與另一個(gè)組合起來。
firstSummaryStatistics.combine(secondSummaryStatistics); System.out.println(firstSummaryStatistics)連接所有的標(biāo)題
private static String allTitles(List<Task> tasks) {return tasks.stream().map(Task::getTitle).collect(joining(", ")); }編寫一個(gè)定制的收集器
import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset;import java.util.Collections; import java.util.EnumSet; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector;public class MultisetCollector<T> implements Collector<T, Multiset<T>, Multiset<T>> {@Overridepublic Supplier<Multiset<T>> supplier() {return HashMultiset::create;}@Overridepublic BiConsumer<Multiset<T>, T> accumulator() {return (set, e) -> set.add(e, 1);}@Overridepublic BinaryOperator<Multiset<T>> combiner() {return (set1, set2) -> {set1.addAll(set2);return set1;};}@Overridepublic Function<Multiset<T>, Multiset<T>> finisher() {return Function.identity();}@Overridepublic Set<Characteristics> characteristics() {return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));} }import com.google.common.collect.Multiset;import java.util.Arrays; import java.util.List;public class MultisetCollectorExample {public static void main(String[] args) {List<String> names = Arrays.asList("shekhar", "rahul", "shekhar");Multiset<String> set = names.stream().collect(new MultisetCollector<>());set.forEach(str -> System.out.println(str + ":" + set.count(str)));} }Java8中的字?jǐn)?shù)統(tǒng)計(jì)
我們將通過使用流和收集器在Java8中編寫有名的字?jǐn)?shù)統(tǒng)計(jì)樣例來結(jié)束這一節(jié)。
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))); }總結(jié)
以上是生活随笔為你收集整理的Java8学习笔记(七)--Collectors的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mybatis if标签判断大小
- 下一篇: JDBC编程专题9之JDBC事务