日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java8学习笔记(七)--Collectors

發布時間:2025/3/15 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java8学习笔记(七)--Collectors 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本系列文章翻譯自@shekhargulati的java8-the-missing-tutorial

你已經學習了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());} }

toList收集器使用了列表的add方法來向結果列表中添加元素。toList收集器使用了ArrayList作為列表的實現。

將數據收集進一個集合

如果我們想要確保返回的標題都是唯一的,并且我們不在乎元素的順序,那么我們可以使用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作為集合的實現來存儲結果集。

將數據收集進一個映射

你可以使用toMap收集器將一個流轉換為一個映射。toMap收集器需要兩個映射方法來獲得映射的鍵和值。在下面展示的代碼中,Task::getTitle是接收一個任務并產生一個只包含該任務標題的鍵的Function。task -> task是一個用來返回任務本身的lambda表達式。

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())); }

從一個流中創建映射的代碼會在存在重復的鍵時拋出異常。你將會得到一個類似下面的錯誤。

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方法的另一個變體來處理重復問題,它允許我們指定一個合并方法。這個合并方法允許用戶他們指定想如何處理多個值關聯到同一個鍵的沖突。在下面展示的代碼中,我們只是使用了新的值,當然你也可以編寫一個智能的算法來處理沖突。

private static Map<String, Task> taskMap_duplicates(List<Task> tasks) {return tasks.stream().collect(toMap(Task::getTitle, identity(), (t1, t2) -> t2)); }

你可以通過使用toMap方法的第三個變體來指定其他的映射實現。這需要你指定將用來存儲結果的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收集器,它產生一個ConcurrentMap而不是HashMap。

使用其它的收集器

像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(";")); }

分類收集器

收集器最常見的使用場景之一是對元素進行分類。讓我來看一下不同的例子來理解我們如何進行分類。

例子1:根據類型對任務分類

我們看一下下面展示的例子,我們想要根據TaskType來對所有的任務進行分類。我們可以通過使用Collectors輔助類中的groupingBy方法來輕易地進行該項任務。你可以通過使用方法引用和靜態導入來使它更加高效。

import static java.util.stream.Collectors.groupingBy; private static Map<TaskType, List<Task>> groupTasksByType(List<Task> tasks) {return tasks.stream().collect(groupingBy(Task::getType)); }

它將會產生如下的輸出。

{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:根據標簽分類

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:根據標簽和數量對任務分類

將分類器和收集器結合起來。

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:根據任務類型和創建日期分類

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

也有其它的變種形式,像針對其它原生類型的LongSummaryStatistics和DoubleSummaryStatistics。

你也可以通過使用combine操作來將一個IntSummaryStatistics與另一個組合起來。

firstSummaryStatistics.combine(secondSummaryStatistics); System.out.println(firstSummaryStatistics)

連接所有的標題

private static String allTitles(List<Task> tasks) {return tasks.stream().map(Task::getTitle).collect(joining(", ")); }

編寫一個定制的收集器

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中的字數統計

我們將通過使用流和收集器在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))); }

總結

以上是生活随笔為你收集整理的Java8学习笔记(七)--Collectors的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。