自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖解Stream之collect:長(zhǎng)文深度分析讓你徹底掌握流式編程

開發(fā) 前端
collect 操作是 Stream 流處理中的關(guān)鍵一步,用于將處理后的元素以指定的方式進(jìn)行收集和匯總。下面我們對(duì)collect相關(guān)的操作原理及方法進(jìn)行詳細(xì)地介紹,確保我們完全掌握collect的使用。

在 Java 8 中,引入了 Stream 流的概念,它是對(duì)集合數(shù)據(jù)進(jìn)行操作的一種高級(jí)抽象。Stream 具有以下幾個(gè)主要特點(diǎn)和優(yōu)勢(shì):

  1. 聲明式編程 通過簡(jiǎn)潔的方式表達(dá)對(duì)數(shù)據(jù)的處理邏輯,而無需關(guān)注具體的實(shí)現(xiàn)細(xì)節(jié)。例如,使用 filter 方法篩選出符合條件的元素,使用 map 方法對(duì)元素進(jìn)行轉(zhuǎn)換。
  2. 懶加載Stream 的操作并非立即執(zhí)行,而是在終端操作(如 collect、forEach 等)被調(diào)用時(shí)才真正執(zhí)行。這有助于提高性能,避免不必要的計(jì)算。
  3. 鏈?zhǔn)讲僮?可以將多個(gè)操作連接在一起,形成一個(gè)連貫的處理流程,使代碼更具可讀性和可維護(hù)性。
  4. 并行處理 可以方便地實(shí)現(xiàn)并行計(jì)算,充分利用多核 CPU 的優(yōu)勢(shì),提高處理大規(guī)模數(shù)據(jù)的效率。

而在 Stream 流中,collect 操作是一個(gè)終端操作,用于將 Stream 中的元素收集到一個(gè)新的集合或數(shù)據(jù)結(jié)構(gòu)中。Stream 提供了對(duì)數(shù)據(jù)的一系列中間操作,如 filter、map、sorted 等,這些操作只是定義了對(duì)數(shù)據(jù)的處理邏輯,但不會(huì)真正執(zhí)行對(duì)數(shù)據(jù)的處理。而 collect 操作作為終端操作,觸發(fā)之前定義的中間操作的執(zhí)行,并將處理后的結(jié)果進(jìn)行收集。

總之,collect 操作是 Stream 流處理中的關(guān)鍵一步,用于將處理后的元素以指定的方式進(jìn)行收集和匯總。下面我們對(duì)collect相關(guān)的操作原理及方法進(jìn)行詳細(xì)地介紹,確保我們完全掌握collect的使用。

Collectors介紹

我們先看看Collect、Collector和Collectors的區(qū)別:

  • collect 是 Java 8 中 Stream 流的一個(gè)方法,用于對(duì)流中的元素進(jìn)行收集操作。它需要傳入一個(gè)實(shí)現(xiàn)了 Collector 接口的收集器來指定具體的收集行為。
  • Collector 是一個(gè)接口,定義了收集流元素的規(guī)范和方法。通過實(shí)現(xiàn) Collector 接口,可以自定義收集器來實(shí)現(xiàn)特定的元素收集邏輯。
  • Collectors 是一個(gè)工具類,它提供了許多靜態(tài)方法,用于方便地創(chuàng)建常見的 Collector 實(shí)現(xiàn)。這些預(yù)定義的收集器可以滿足大多數(shù)常見的收集需求,例如將流元素收集到列表、集合、映射等,或者進(jìn)行分組、分區(qū)、規(guī)約匯總等操作。

例如,使用 Collectors.toList() 可以創(chuàng)建一個(gè)將流元素收集到列表的收集器,然后將其傳遞給 collect 方法,對(duì)流進(jìn)行收集操作并得到一個(gè)包含所有元素的列表。

圖片圖片

概括來說:

  • collect 是 Stream 流的終止方法,使用傳入的收集器(必須是 Collector 接口的某個(gè)具體實(shí)現(xiàn)類)對(duì)結(jié)果執(zhí)行相關(guān)操作。
  • Collector 是一個(gè)接口,collect 方法接收的收集器是 Collector 接口的具體實(shí)現(xiàn)類。
  • Collectors 是一個(gè)工具類,提供了很多靜態(tài)工廠方法,用于創(chuàng)建各種預(yù)定義的 Collector 接口的具體實(shí)現(xiàn)類,方便程序員使用。如果不使用 Collectors 類,自己去實(shí)現(xiàn) Collector 接口也是可以的。

圖片圖片

Collectors的方法

圖片圖片

恒等處理

指的就是Stream的元素在經(jīng)過Collector函數(shù)處理前后完全不變,例如toList()操作,只是最終將結(jié)果從Stream中取出放入到List對(duì)象中,并沒有對(duì)元素本身做任何的更改處理。

圖片圖片

歸約匯總

Stream流中的元素被逐個(gè)遍歷,進(jìn)入到Collector處理函數(shù)中,然后會(huì)與上一個(gè)元素的處理結(jié)果進(jìn)行合并處理,并得到一個(gè)新的結(jié)果,以此類推,直到遍歷完成后,輸出最終的結(jié)果。

圖片圖片

分組分區(qū)

Collectors工具類中提供了groupingBy和partitioningBy方法進(jìn)行數(shù)據(jù)分區(qū),區(qū)別在于partitioningBy僅基于條件分成兩個(gè)組。

圖片圖片

Collector的原理

要自定義收集器Collector,需要實(shí)現(xiàn)Collector接口中定義的五個(gè)方法,分別是:supplier()、accumulator()、combiner()、finisher()和characteristics()。

圖片圖片

這5個(gè)方法的含義說明歸納如下:

接口名稱

功能含義說明

supplier

創(chuàng)建新的結(jié)果容器,可以是一個(gè)容器,也可以是一個(gè)累加器實(shí)例,總之是用來存儲(chǔ)結(jié)果數(shù)據(jù)的

accumlator

元素進(jìn)入收集器中的具體處理操作

finisher

當(dāng)所有元素都處理完成后,在返回結(jié)果前的對(duì)結(jié)果的最終處理操作,當(dāng)然也可以選擇不做任何處理,直接返回

combiner

各個(gè)子流的處理結(jié)果最終如何合并到一起去,比如并行流處理場(chǎng)景,元素會(huì)被切分為好多個(gè)分片進(jìn)行并行處理,最終各個(gè)分片的數(shù)據(jù)需要合并為一個(gè)整體結(jié)果,即通過此方法來指定子結(jié)果的合并邏輯

characteristics

對(duì)此收集器處理行為的補(bǔ)充描述,比如此收集器是否允許并行流中處理,是否finisher方法必須要有等等,此處返回一個(gè)Set集合,里面的候選值是固定的幾個(gè)可選項(xiàng)。

對(duì)于characteristics返回set集合中的可選值,說明如下:

取值

含義說明

UNORDERED

無序。聲明此收集器的匯總歸約結(jié)果與Stream流元素遍歷順序無關(guān),不受元素處理順序影響

CONCURRENT

并行。聲明此收集器可以多個(gè)線程并行處理,允許并行流中進(jìn)行處理

IDENTITY_FINISH

恒等映射。聲明此收集器的finisher方法是一個(gè)恒等操作

現(xiàn)在,我們知道了這5個(gè)接口方法各自的含義與用途了,那么作為一個(gè)Collector收集器,這幾個(gè)接口之間是如何配合處理并將Stream數(shù)據(jù)收集為需要的輸出結(jié)果的呢?下面這張圖可以清晰的闡述這一過程:

圖片圖片

如果我們的Collector是支持在并行流中使用的,則其處理過程有所不同:

圖片圖片

下面的例子展示如何自定義一個(gè)將元素收集到LinkedList的收集器:

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
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 MyCollector implements Collector<String, List<String>, List<String>> {

    // supplier()方法返回一個(gè)Supplier,它創(chuàng)建了一個(gè)空的LinkedList實(shí)例,作為收集數(shù)據(jù)的容器。
    @Override
    public Supplier<List<String>> supplier() {
        return LinkedList::new; 
    }

    // accumulator()方法返回一個(gè)BiConsumer,用于將流中的元素添加到LinkedList中。
    @Override
    public BiConsumer<List<String>, String> accumulator() {
        return List::add; 
    }

    // combiner()方法返回一個(gè)BinaryOperator,用于合并多個(gè)LinkedList。當(dāng)流被并行處理時(shí),可能會(huì)有多個(gè)子部分的結(jié)果需要合并,這里將兩個(gè)LinkedList合并為一個(gè)。
    @Override
    public BinaryOperator<List<String>> combiner() {
        return (r1, r2) -> {
            r1.addAll(r2);
            return r1;
        };
    }

    // finisher()方法返回一個(gè)Function,在遍歷完流后,將累加器對(duì)象(在這里就是LinkedList本身)轉(zhuǎn)換為最終結(jié)果。在這個(gè)例子中,累加器對(duì)象就是最終結(jié)果,所以直接返回它。
    @Override
    public Function<List<String>, List<String>> finisher() {
        return list -> list; 
    }

    // characteristics()方法返回一個(gè)包含收集器特征的EnumSet。這里使用了IDENTITY_FINISH特征,表示finisher方法返回的是一個(gè)恒等函數(shù),可以跳過,直接將累加器作為最終結(jié)果。
    @Override
    public EnumSet<Collector.Characteristics> characteristics() {
        return EnumSet.of(Collector.Characteristics.IDENTITY_FINISH); 
    }
}

下面我們用自定義的收集器進(jìn)行處理:

List<String> input = Arrays.asList("apple", "banana", "orange");
List<String> result = input.stream().collect(new MyCollector());

如果希望收集器具有其他特性,例如支持并行處理(CONCURRENT)、不保證元素順序(UNORDERED)等,可以在characteristics()方法中添加相應(yīng)的特性。例如,如果你的收集器支持并行處理且不保證元素順序,可以這樣返回特性集合:

return EnumSet.of(Collector.Characteristics.CONCURRENT, Collector.Characteristics.UNORDERED);

另外,還可以根據(jù)具體的需求自定義收集器的邏輯,例如過濾元素、執(zhí)行特定的計(jì)算等。

Collectors方法深究

groupingBy分組

Collectors.groupingBy是 Java 8 中Stream API 的一個(gè)收集器,用于將流中的元素根據(jù)某個(gè)分類函數(shù)收集到Map中。

groupingBy的構(gòu)造方法

  • groupingBy(Function):基本的分組,默認(rèn)使用List收集,

圖片圖片

相當(dāng)于groupingBy(classifier, toList())。我們用下面的代碼實(shí)現(xiàn),對(duì)學(xué)生按照年齡段進(jìn)行分組:

Map<Integer, List<Student>> nameListByAge = students.stream().collect(Collectors.groupingBy(Student::getAge));
  • groupingBy(Function, Collector):可指定收集器的分組

圖片圖片

這里使用Set集合收集。

// 不同年齡段的學(xué)生集合,去重
Map<Integer, Set<String>> namesByAge = students.stream().collect(Collectors.groupingBy(
        Student::getAge,
        Collectors.mapping(Student::getName, Collectors.toSet()))
);
  • groupingBy(Function, Supplier, Collector):可指定存儲(chǔ)容器和收集器的分組

圖片圖片

下面使用TreeMap作為容器,保證了鍵的有序性。但是分組之后的組內(nèi)數(shù)據(jù)不是有序的。

// 【鍵有序】不同年齡段的學(xué)生集合,去重,年齡按照升序排列
Map<Integer, Set<String>> namesBySortedAge = students.stream().collect(Collectors.groupingBy(
    Student::getAge,
    TreeMap::new,
    Collectors.mapping(Student::getName, Collectors.toSet()))
);

如果要保證分組之后的數(shù)據(jù)有序,有下面兩種方法:

  • collectingAndThen:先分組,再使用collectingAndThen聚合操作,對(duì)組內(nèi)數(shù)據(jù)進(jìn)行排序。
Map<Integer, List<Student>> sortedCollect = students.stream()
            .collect(Collectors.groupingBy(
                    Student::getAge,
                    Collectors.collectingAndThen(
                            // 先收集到List
                            Collectors.toList(),
                            // 然后對(duì)每個(gè)List進(jìn)行排序
                            list -> list.stream().sorted(Comparator.comparing(Student::getScore)).collect(Collectors.toList())
                    )
            ));
  • mapping:使用第二種構(gòu)造方法,對(duì)組內(nèi)元素收集到list,然后使用TreeSet集合進(jìn)行收集。
// 按照年齡分組,組內(nèi)按照分?jǐn)?shù)升序
Map<Integer, TreeSet<Student>> collect = students.stream().collect(Collectors.groupingBy(
        Student::getAge,
        Collectors.mapping(student -> student, Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getScore))))
        )
);

基礎(chǔ)分組功能

  • 按照對(duì)象的某個(gè)字段進(jìn)行分組:假設(shè)有一個(gè)學(xué)生類Student,包含course(課程)字段,可以按照課程對(duì)學(xué)生進(jìn)行分組。
Map<String, List<Student>> groupByCourse = students.stream()
  .collect(Collectors.groupingBy(Student::getCourse));
  • 自定義鍵的映射:根據(jù)學(xué)生對(duì)象的多個(gè)字段或進(jìn)行某種格式化操作來生成鍵。
Map<String, List<Student>> groupByCustomKey = students.stream()
  .collect(Collectors.groupingBy(student -> student.getName() + "_" + student.getAge()));
  • 自定義容器類型:如使用LinkedHashMap保證分組后鍵的有序性。
Map<String, List<Student>> groupByCourseWithLinkedHashMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, LinkedHashMap::new, Collectors.toList()));

分組統(tǒng)計(jì)功能

  • 計(jì)數(shù):計(jì)算每個(gè)分組中的元素?cái)?shù)量。
Map<String, Long> courseCountMap = students.stream()
  .collect(Collectors.groupingBy(Student::getCourse, Collectors.counting()));
  • 求和:對(duì)每個(gè)分組中的某個(gè)數(shù)值字段進(jìn)行求和。
Map<String, Integer> totalScoreByCourseMap = students.stream()
  .collect(Collectors.groupingBy(Student::getCourse, Collectors.summingInt(Student::getScore)));
  • 平均值:計(jì)算每個(gè)分組中某個(gè)數(shù)值字段的平均值。
Map<String, Double> averageScoreByCourseMap = students.stream()
  .collect(Collectors.groupingBy(Student::getCourse, Collectors.averagingInt(Student::getScore)));
  • 最大最小值:獲取每個(gè)分組中某個(gè)數(shù)值字段的最大值或最小值。
Map<String, Student> maxScoreStudentByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.maxBy(Comparator.comparingInt(Student::getScore))));

Map<String, Student> minScoreStudentByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.minBy(Comparator.comparingInt(Student::getScore))));
  • 完整統(tǒng)計(jì):同時(shí)獲取計(jì)數(shù)、總和、平均值、最大最小值等統(tǒng)計(jì)結(jié)果。
Map<String, IntSummaryStatistics> summaryStatisticsByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.summarizingInt(Student::getScore)));
  • 范圍統(tǒng)計(jì):根據(jù)某個(gè)條件進(jìn)行范圍分組統(tǒng)計(jì)。
Map<Boolean, List<Student>> dividedByScore = students.stream()
 .collect(Collectors.partitioningBy(student -> student.getScore() >= 60));

分組合并功能

合并分組結(jié)果:使用reducing方法對(duì)每個(gè)分組的元素進(jìn)行自定義的合并操作。

Map<String, String> combinedNamesByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.reducing("", Student::getName, (name1, name2) -> name1 + ", " + name2)));

合并字符串:將每個(gè)分組中的字符串元素連接起來。

Map<String, String> joinedNamesByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.joining(", ")));

分組自定義映射功能

映射結(jié)果為Collection對(duì)象:將每個(gè)分組的元素映射為另一個(gè)Collection對(duì)象。

Map<String, Set<Student>> studentsSetByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.toSet()));

自定義映射結(jié)果:通過mapping方法進(jìn)行更復(fù)雜的映射操作。

Map<String, List<String>> studentNamesByCourseMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, Collectors.mapping(Student::getName, Collectors.toList())));

自定義downstream收集器:更靈活地控制分組后的值的收集方式。

Collector<Student,?, Map<String, CustomResult>> customCollector = Collector.of(
    HashMap::new, 
    (map, student) -> { 
        // 自定義的收集邏輯,將學(xué)生對(duì)象轉(zhuǎn)換為 CustomResult 并添加到 map 中 
    },
    (map1, map2) -> { 
        // 合并兩個(gè) map 的邏輯 
    });

Map<String, CustomResult> customResultMap = students.stream()
 .collect(Collectors.groupingBy(Student::getCourse, customCollector));

多級(jí)分組可以通過嵌套使用groupingBy來實(shí)現(xiàn)。例如,假設(shè)有一個(gè)包含學(xué)生信息的列表,要先按班級(jí)分組,然后在每個(gè)班級(jí)內(nèi)再按性別分組,可以這樣寫:

Map<String, Map<String, List<Student>>> groupedByClassAndGender = students.stream()
  .collect(Collectors.groupingBy(Student::getClass, Collectors.groupingBy(Student::getGender)));

在上述示例中,外層的groupingBy按照班級(jí)進(jìn)行分組,得到的每個(gè)班級(jí)的分組結(jié)果(本身也是一個(gè)Map)又通過內(nèi)層的groupingBy按照性別進(jìn)一步分組。這樣最終得到的是一個(gè)兩級(jí)分組的Map結(jié)構(gòu)。

partitioningBy分類

掌握了groupingBy,現(xiàn)在看partitioningBy就簡(jiǎn)單很多了。就兩個(gè)簡(jiǎn)單的構(gòu)造方法:

// 僅提供分類器
partitioningBy(Predicate<? super T> predicate) 

// 提供分類器和下游收集器
partitioningBy(Predicate<? super T> predicate,Collector<? super T, A, D> downstream)

比如我們篩選成年人和非成年人:

Map<Boolean, List<Student>> adultList = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 18));

Map<Boolean, Set<Student>> adultSet = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 18, Collectors.toSet()));

結(jié)果如下圖所示:

圖片圖片

collectingAndThen分組處理

圖片圖片

從方法簽名可以看出,需要傳入一個(gè)收集器和一個(gè)處理函數(shù),相當(dāng)于收集了數(shù)據(jù)之后,再進(jìn)行后續(xù)操作。如下圖所示:

圖片圖片

比如,前面提到的,先分組,再排序:

Map<Integer, List<Student>> sortedCollect = students.stream()
            .collect(Collectors.groupingBy(
                    Student::getAge,
                    Collectors.collectingAndThen(
                            // 先收集到List
                            Collectors.toList(),
                            // 然后對(duì)每個(gè)List進(jìn)行排序
                            list -> list.stream().sorted(Comparator.comparing(Student::getScore)).collect(Collectors.toList())
                    )
            ));

reducing歸集操作

單參數(shù):輸入歸集操作

  • BinaryOperator accumulator 歸集操作函數(shù) 輸入?yún)?shù)T返回T

圖片圖片

比如實(shí)現(xiàn)數(shù)組的內(nèi)容求和:

List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> sum = testData.stream().collect(Collectors.reducing((prev, cur) -> {
    System.out.println("prev=>" + prev + "cur=>" + cur);
    return prev + cur;
}));
System.out.print(sum.get()); // 45

雙參數(shù):輸入初始值、歸集操作 參數(shù)說明

  • T identity 返回類型T初始值
  • BinaryOperator accumulator 歸集操作函數(shù) 輸入?yún)?shù)T返回T

下面是增加了初始值的求和操作:

List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer sum = testData.stream().collect(Collectors.reducing(20, (prev, cur) -> {
    System.out.println("prev=>" + prev + "cur=>" + cur);
    return prev + cur;
}));
System.out.print(sum); //65

三參數(shù):這個(gè)函數(shù)才是真正體現(xiàn)reducing(歸集)的過程。調(diào)用者要明確知道以下三點(diǎn)

  1. 需要轉(zhuǎn)換類型的初始值
  2. 類型如何轉(zhuǎn)換
  3. 如何收集返回值

參數(shù)說明

  • U identity 最終返回類型U初始值
  • BiFunction<U, ? super T, U> accumulator, 將輸入?yún)?shù)T轉(zhuǎn)換成返回類型U的函數(shù)
  • BinaryOperator  combiner 歸集操作函數(shù) 輸入?yún)?shù)U返回U

圖片圖片

比如實(shí)現(xiàn)單數(shù)字轉(zhuǎn)字符串并按逗號(hào)連接的功能:

List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
String joinStr = testData.stream().collect(Collectors.reducing("轉(zhuǎn)換成字符串", in -> {
    return in + "";
}, (perv, cur) -> {
    return perv + "," + cur;
}));
System.out.print(joinStr); // 轉(zhuǎn)換成字符串,1,2,3,4,5,6,7,8,9

責(zé)任編輯:武曉燕 來源: 松語(yǔ)編程
相關(guān)推薦

2024-12-02 10:15:15

2021-04-15 07:32:02

java 代碼Stream

2021-08-11 22:17:48

負(fù)載均衡LVS機(jī)制

2023-07-06 08:31:50

Python對(duì)象編程

2020-12-08 08:14:11

SQL注入數(shù)據(jù)庫(kù)

2019-09-24 08:16:14

Reactor響應(yīng)式編程

2019-07-11 14:45:52

簡(jiǎn)歷編程項(xiàng)目

2009-11-06 09:39:40

WCF契約

2024-03-15 08:23:26

異步編程函數(shù)

2014-11-05 10:58:00

編程

2016-03-28 09:39:54

2019-02-25 09:20:53

2024-06-21 09:27:05

2024-04-12 09:01:08

2022-09-16 08:32:17

Reduxreact

2021-04-18 07:09:50

工具類異步編程

2023-05-29 08:11:42

@Value注解Bean

2020-11-03 10:32:48

回調(diào)函數(shù)模塊

2017-06-07 18:40:33

PromiseJavascript前端

2024-01-17 08:18:14

RPAJava技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)