Java 8之Stream

一、Java 8 Stream簡(jiǎn)介

Java 8 API添加了一個(gè)新的抽象稱為流Stream仇矾,可以讓你以一種聲明的方式處理數(shù)據(jù)贮匕。Stream 使用一種類似用 SQL 語(yǔ)句從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)的直觀方式來(lái)提供一種對(duì) Java 集合運(yùn)算和表達(dá)的高階抽象掏膏。

1.1 什么是 Stream馒疹?

Stream(流)是一個(gè)來(lái)自數(shù)據(jù)源的元素隊(duì)列并支持聚合操作

  • 元素 是特定類型的對(duì)象颖变,形成一個(gè)隊(duì)列悼做。 Java中的Stream并不會(huì)存儲(chǔ)元素肛走,而是按需計(jì)算朽色。
  • 數(shù)據(jù)源 流的來(lái)源葫男,可以是集合梢褐,數(shù)組盈咳,I/O channel鸣剪, 產(chǎn)生器generator 等筐骇。
  • 聚合操作 類似SQL語(yǔ)句一樣的操作铛纬, 比如filter, map, reduce, find, match, sorted等告唆。

和以前的Collection操作不同悔详, Stream操作還有兩個(gè)基礎(chǔ)的特征:

  • Pipelining: 中間操作都會(huì)返回流對(duì)象本身茄螃。 這樣多個(gè)操作可以串聯(lián)成一個(gè)管道, 如同流式風(fēng)格(fluent style)拼弃。 這樣做可以對(duì)操作進(jìn)行優(yōu)化吻氧, 比如延遲執(zhí)行(laziness)和短路( short-circuiting)盯孙。
  • 內(nèi)部迭代: 以前對(duì)集合遍歷都是通過(guò)Iterator或者For-Each的方式, 顯式的在集合外部進(jìn)行迭代振惰, 這叫做外部迭代骑晶。 Stream提供了內(nèi)部迭代的方式, 通過(guò)訪問(wèn)者模式(Visitor)實(shí)現(xiàn)羽圃。
1.2 流式處理

我們希望對(duì)一個(gè)包含整數(shù)的集合中篩選出所有的偶數(shù)朽寞,并將其封裝成為一個(gè)新的List返回脑融,那么在Java8之前肘迎,我們需要通過(guò)如下代碼實(shí)現(xiàn)

List<Integer> evens = new ArrayList<>();
for (final Integer num : nums) {
    if (num % 2 == 0) {
        evens.add(num);
    }
}

通過(guò)java8的流式處理姻蚓,我們可以將代碼簡(jiǎn)化為

List<Integer> evens = nums.stream().filter(num -> num % 2 == 0).collect(Collectors.toList());

先簡(jiǎn)單解釋一下上面這行語(yǔ)句的含義狰挡,stream()操作將集合轉(zhuǎn)換成一個(gè)流加叁,filter()執(zhí)行我們自定義的篩選處理,這里是通過(guò)lambda表達(dá)式篩選出所有偶數(shù)豫柬,最后我們通過(guò)collect()對(duì)結(jié)果進(jìn)行封裝處理轮傍,并通過(guò)Collectors.toList()指定其封裝成為一個(gè)List集合返回创夜。

由上面的例子可以看出,Java8的流式處理極大的簡(jiǎn)化了對(duì)于集合的操作檬贰,實(shí)際上不光是集合翁涤,包括數(shù)組葵礼、文件等,只要是可以轉(zhuǎn)換成流届谈,我們都可以借助流式處理艰山,類似于我們寫SQL語(yǔ)句一樣對(duì)其進(jìn)行操作曙搬。Java8通過(guò)內(nèi)部迭代來(lái)實(shí)現(xiàn)對(duì)流的處理,一個(gè)流式處理可以分為三個(gè)部分:轉(zhuǎn)換成流搂擦、中間操作瀑踢、終端操作。

以集合為例棘劣,一個(gè)流式處理的操作我們首先需要調(diào)用stream()函數(shù)將其轉(zhuǎn)換成流茬暇,然后再調(diào)用相應(yīng)的中間操作達(dá)到我們需要對(duì)集合進(jìn)行的操作糙俗,比如篩選、轉(zhuǎn)換等劈彪,最后通過(guò)終端操作對(duì)前面的結(jié)果進(jìn)行封裝,返回我們需要的形式扼仲。

1.3 Java7與Java8實(shí)現(xiàn)代碼對(duì)比
1.3.1 Java7代碼
import java.util.*;

/**
 * @author Alan Chen
 * @description Java7
 * @date 2021/11/22
 */
public class Java7Tester {

    public static void main(String args[]) {
        System.out.println("使用 Java 7: ");

        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        System.out.println("列表: " + strings);

        // 計(jì)算空字符串
        long count = getCountEmptyStringUsingJava7(strings);
        System.out.println("空字符數(shù)量為: " + count);

        // 字符串長(zhǎng)度為3的數(shù)量為
        count = getCountLength3UsingJava7(strings);
        System.out.println("字符串長(zhǎng)度為3的數(shù)量為: " + count);

        // 刪除空字符串
        List<String> filtered = deleteEmptyStringsUsingJava7(strings);
        System.out.println("刪除空字符串后的列表: " + filtered);

        // 刪除空字符串驰后,并使用逗號(hào)把它們合并起來(lái)
        String mergedString = getMergedStringUsingJava7(strings, ", ");
        System.out.println("合并字符串: " + mergedString);

        // 獲取列表元素平方數(shù)
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        List<Integer> squaresList = getSquares(numbers);
        System.out.println("平方數(shù)列表: " + squaresList);

        //列表數(shù)據(jù)統(tǒng)計(jì)
        List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
        System.out.println("列表: " + integers);
        System.out.println("列表中最大的數(shù) : " + getMax(integers));
        System.out.println("列表中最小的數(shù) : " + getMin(integers));
        System.out.println("所有數(shù)之和 : " + getSum(integers));
        System.out.println("平均數(shù) : " + getAverage(integers));
        System.out.println("隨機(jī)數(shù): ");

        // 輸出10個(gè)隨機(jī)數(shù)
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            System.out.println(random.nextInt());
        }
    }


    private static int getCountEmptyStringUsingJava7(List<String> strings) {
        int count = 0;

        for (String string : strings) {

            if (string.isEmpty()) {
                count++;
            }
        }
        return count;
    }

    private static int getCountLength3UsingJava7(List<String> strings) {
        int count = 0;

        for (String string : strings) {

            if (string.length() == 3) {
                count++;
            }
        }
        return count;
    }

    private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
        List<String> filteredList = new ArrayList<String>();

        for (String string : strings) {

            if (!string.isEmpty()) {
                filteredList.add(string);
            }
        }
        return filteredList;
    }

    private static String getMergedStringUsingJava7(List<String> strings, String separator) {
        StringBuilder stringBuilder = new StringBuilder();

        for (String string : strings) {

            if (!string.isEmpty()) {
                stringBuilder.append(string);
                stringBuilder.append(separator);
            }
        }
        String mergedString = stringBuilder.toString();
        return mergedString.substring(0, mergedString.length() - 2);
    }

    private static List<Integer> getSquares(List<Integer> numbers) {
        List<Integer> squaresList = new ArrayList<Integer>();

        for (Integer number : numbers) {
            Integer square = new Integer(number.intValue() * number.intValue());

            if (!squaresList.contains(square)) {
                squaresList.add(square);
            }
        }
        return squaresList;
    }

    private static int getMax(List<Integer> numbers) {
        int max = numbers.get(0);

        for (int i = 1; i < numbers.size(); i++) {

            Integer number = numbers.get(i);

            if (number.intValue() > max) {
                max = number.intValue();
            }
        }
        return max;
    }

    private static int getMin(List<Integer> numbers) {
        int min = numbers.get(0);

        for (int i = 1; i < numbers.size(); i++) {
            Integer number = numbers.get(i);

            if (number.intValue() < min) {
                min = number.intValue();
            }
        }
        return min;
    }

    private static int getSum(List numbers) {
        int sum = (int) (numbers.get(0));

        for (int i = 1; i < numbers.size(); i++) {
            sum += (int) numbers.get(i);
        }
        return sum;
    }

    private static int getAverage(List<Integer> numbers) {
        return getSum(numbers) / numbers.size();
    }
}

結(jié)果

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" "-javaagent:D:\install\IntelliJ IDEA 2020.1\lib\idea_rt.jar=57428:D:\install\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;E:\code\test\out\production\test" Java7Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符數(shù)量為: 2
字符串長(zhǎng)度為3的數(shù)量為: 3
刪除空字符串后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方數(shù)列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的數(shù) : 19
列表中最小的數(shù) : 1
所有數(shù)之和 : 85
平均數(shù) : 9
隨機(jī)數(shù): 
-1621840016
1848281340
-956050294
1802920185
-1521363590
1796922091
2027850453
1804093167
-840853541
-1529616196

Process finished with exit code 0

1.3.2 Java8代碼
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Alan Chen
 * @description Java8
 * @date 2021/11/22
 */
public class Java8Tester {

    public static void main(String args[]) {

        System.out.println("使用 Java 8: ");
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        System.out.println("列表: " + strings);

        // 空字符串?dāng)?shù)量為
        long count = strings.stream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串?dāng)?shù)量為: " + count);

        // 字符串長(zhǎng)度為3的數(shù)量為
        count = strings.stream().filter(string -> string.length() == 3).count();
        System.out.println("字符串長(zhǎng)度為3的數(shù)量為: " + count);

        // 刪除空字符串后的列表
        List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
        System.out.println("刪除空字符串后的列表: " + filtered);

        // 刪除空字符串属愤,并使用逗號(hào)把它們合并起來(lái)
        String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
        System.out.println("合并字符串: " + mergedString);

        // 獲取列表元素平方數(shù)
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
        System.out.println("Squares List: " + squaresList);

        //列表數(shù)據(jù)統(tǒng)計(jì)
        List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
        System.out.println("列表: " + integers);

        IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
        System.out.println("列表中最大的數(shù) : " + stats.getMax());
        System.out.println("列表中最小的數(shù) : " + stats.getMin());
        System.out.println("所有數(shù)之和 : " + stats.getSum());
        System.out.println("平均數(shù) : " + stats.getAverage());
        System.out.println("隨機(jī)數(shù): ");

        // 輸出10個(gè)隨機(jī)數(shù)
        Random random = new Random();
        random.ints().limit(10).sorted().forEach(System.out::println);

        // 并行處理
        count = strings.parallelStream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串的數(shù)量為: " + count);
    }
}

結(jié)果

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" "-javaagent:D:\install\IntelliJ IDEA 2020.1\lib\idea_rt.jar=57436:D:\install\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;E:\code\test\out\production\test" Java8Tester
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串?dāng)?shù)量為: 2
字符串長(zhǎng)度為3的數(shù)量為: 3
刪除空字符串后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的數(shù) : 19
列表中最小的數(shù) : 1
所有數(shù)之和 : 85
平均數(shù) : 9.444444444444445
隨機(jī)數(shù): 
-788107954
-559847792
61268113
334297559
568203527
637005978
674422513
809980552
1415190011
1892961238
空字符串的數(shù)量為: 2

Process finished with exit code 0

二贱呐、中間操作

我們定義一個(gè)簡(jiǎn)單的學(xué)生實(shí)體類奄薇,用于后面的例子演示

public class Student {

    /** 學(xué)號(hào) */
    private long id;

    private String name;

    private int age;

    /** 年級(jí) */
    private int grade;

    /** 專業(yè) */
    private String major;

    /** 學(xué)校 */
    private String school;

    // 省略getter和setter
}
// 初始化
List<Student> students = new ArrayList<Student>() {
    {
        add(new Student(20160001, "孔明", 20, 1, "土木工程", "武漢大學(xué)"));
        add(new Student(20160002, "伯約", 21, 2, "信息安全", "武漢大學(xué)"));
        add(new Student(20160003, "玄德", 22, 3, "經(jīng)濟(jì)管理", "武漢大學(xué)"));
        add(new Student(20160004, "云長(zhǎng)", 21, 2, "信息安全", "武漢大學(xué)"));
        add(new Student(20161001, "翼德", 21, 2, "機(jī)械與自動(dòng)化", "華中科技大學(xué)"));
        add(new Student(20161002, "元直", 23, 4, "土木工程", "華中科技大學(xué)"));
        add(new Student(20161003, "奉孝", 23, 4, "計(jì)算機(jī)科學(xué)", "華中科技大學(xué)"));
        add(new Student(20162001, "仲謀", 22, 3, "土木工程", "浙江大學(xué)"));
        add(new Student(20162002, "魯肅", 23, 4, "計(jì)算機(jī)科學(xué)", "浙江大學(xué)"));
        add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大學(xué)"));
    }
};
2.1 過(guò)濾

過(guò)濾搞隐,顧名思義就是按照給定的要求對(duì)集合進(jìn)行篩選滿足條件的元素劣纲,java8提供的篩選操作包括:filter、distinct绷柒、limit废睦、skip嗜湃。

2.1.1 filter

在前面的例子中我們已經(jīng)演示了如何使用filter杖挣,其定義為:Stream<T> filter(Predicate<? super T> predicate)惩妇,filter接受一個(gè)謂詞Predicate,我們可以通過(guò)這個(gè)謂詞定義篩選條件氓皱,Predicate是一個(gè)函數(shù)式接口,其包含一個(gè)test(T t)方法朵你,該方法返回boolean。現(xiàn)在我們希望從集合students中篩選出所有武漢大學(xué)的學(xué)生忌傻,那么我們可以通過(guò)filter來(lái)實(shí)現(xiàn)水孩,并將篩選操作作為參數(shù)傳遞給filter

List<Student> whuStudents = students.stream()
                                    .filter(student -> "武漢大學(xué)".equals(student.getSchool()))
                                    .collect(Collectors.toList());
2.1.2 去重
2.1.2.1 distinct去重

distinct操作類似于我們?cè)趯慡QL語(yǔ)句時(shí)全肮,添加的DISTINCT關(guān)鍵字牢酵,用于去重處理馍乙,distinct基于Object.equals(Object)實(shí)現(xiàn)吊输,回到最開(kāi)始的例子季蚂,假設(shè)我們希望篩選出所有不重復(fù)的偶數(shù)扭屁,那么可以添加distinct操作

List<Integer> evens = nums.stream()
                        .filter(num -> num % 2 == 0).distinct()
                        .collect(Collectors.toList());

List集合去重

public static void main(String[] args) {
        List<String> memberIds = new ArrayList<>();
        memberIds.add("A");
        memberIds.add("B");
        memberIds.add("A");

        List<String> distinctList = memberIds.stream().distinct().collect(Collectors.toList());
        
        //[A, B]
        System.out.println(distinctList);
    }
2.1.2.2 對(duì)象去重
 List<DynamicDetailDTO> detailDTOS = new ArrayList<>();

 //去重
        detailDTOS = detailDTOS.stream().collect(
                Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(DynamicDetailDTO::getId))), ArrayList::new));
2.1.3 limit

limit操作也類似于SQL語(yǔ)句中的LIMIT關(guān)鍵字,不過(guò)相對(duì)功能較弱高每,limit返回包含前n個(gè)元素的流鲸匿,當(dāng)集合大小小于n時(shí)带欢,則返回實(shí)際長(zhǎng)度,比如下面的例子返回前兩個(gè)專業(yè)為土木工程專業(yè)的學(xué)生

List<Student> civilStudents = students.stream()
                                    .filter(student -> "土木工程".equals(student.getMajor())).limit(2)
                                    .collect(Collectors.toList());

2.1.4 sorted

說(shuō)到limit渡贾,不得不提及一下另外一個(gè)流操作:sorted。該操作用于對(duì)流中元素進(jìn)行排序府怯,sorted要求待比較的元素必須實(shí)現(xiàn)Comparable接口牺丙,如果沒(méi)有實(shí)現(xiàn)也不要緊,我們可以將比較器作為參數(shù)傳遞給sorted(Comparator<? super T> comparator),比如我們希望篩選出專業(yè)為土木工程的學(xué)生冲簿,并按年齡從小到大排序粟判,篩選出年齡最小的兩個(gè)學(xué)生,那么可以實(shí)現(xiàn)為

List<Student> sortedCivilStudents = students.stream()
                                            .filter(student -> "土木工程".equals(student.getMajor())).sorted((s1, s2) -> s1.getAge() - s2.getAge())
                                            .limit(2)
                                            .collect(Collectors.toList());

2.1.4.1 多字段排序
List<類> list; 代表某集合
 
//返回 對(duì)象集合以類屬性一升序排序
 
list.stream().sorted(Comparator.comparing(類::屬性一));
 
//返回 對(duì)象集合以類屬性一降序排序 注意兩種寫法
 
list.stream().sorted(Comparator.comparing(類::屬性一).reversed());//先以屬性一升序,結(jié)果進(jìn)行屬性一降序
 
list.stream().sorted(Comparator.comparing(類::屬性一,Comparator.reverseOrder()));//以屬性一降序
 
//返回 對(duì)象集合以類屬性一升序 屬性二升序
 
list.stream().sorted(Comparator.comparing(類::屬性一).thenComparing(類::屬性二));
 
//返回 對(duì)象集合以類屬性一降序 屬性二升序 注意兩種寫法
 
list.stream().sorted(Comparator.comparing(類::屬性一).reversed().thenComparing(類::屬性二));//先以屬性一升序,升序結(jié)果進(jìn)行屬性一降序,再進(jìn)行屬性二升序
 
list.stream().sorted(Comparator.comparing(類::屬性一,Comparator.reverseOrder()).thenComparing(類::屬性二));//先以屬性一降序,再進(jìn)行屬性二升序
 
//返回 對(duì)象集合以類屬性一降序 屬性二降序 注意兩種寫法
 
list.stream().sorted(Comparator.comparing(類::屬性一).reversed().thenComparing(類::屬性二,Comparator.reverseOrder()));//先以屬性一升序,升序結(jié)果進(jìn)行屬性一降序,再進(jìn)行屬性二降序
 
list.stream().sorted(Comparator.comparing(類::屬性一,Comparator.reverseOrder()).thenComparing(類::屬性二,Comparator.reverseOrder()));//先以屬性一降序,再進(jìn)行屬性二降序
 
//返回 對(duì)象集合以類屬性一升序 屬性二降序 注意兩種寫法
 
list.stream().sorted(Comparator.comparing(類::屬性一).reversed().thenComparing(類::屬性二).reversed());//先以屬性一升序,升序結(jié)果進(jìn)行屬性一降序,再進(jìn)行屬性二升序,結(jié)果進(jìn)行屬性一降序?qū)傩远敌? 
list.stream().sorted(Comparator.comparing(類::屬性一).thenComparing(類::屬性二,Comparator.reverseOrder()));//先以屬性一升序,再進(jìn)行屬性二降序<br><br><br>
2.1.5 skip

skip操作與limit操作相反峦剔,如同其字面意思一樣档礁,是跳過(guò)前n個(gè)元素吝沫,比如我們希望找出排序在2之后的土木工程專業(yè)的學(xué)生呻澜,那么可以實(shí)現(xiàn)為

List<Student> civilStudents = students.stream()
                                    .filter(student -> "土木工程".equals(student.getMajor()))
                                    .skip(2)
                                    .collect(Collectors.toList());

通過(guò)skip,就會(huì)跳過(guò)前面兩個(gè)元素惨险,返回由后面所有元素構(gòu)造的流羹幸,如果n大于滿足條件的集合的長(zhǎng)度,則會(huì)返回一個(gè)空的集合辫愉。

2.2 映射

在SQL中栅受,借助SELECT關(guān)鍵字后面添加需要的字段名稱,可以僅輸出我們需要的字段數(shù)據(jù)恭朗,而流式處理的映射操作也是實(shí)現(xiàn)這一目的屏镊,在java8的流式處理中,主要包含兩類映射操作:map和flatMap痰腮。

2.2.1 map

舉例說(shuō)明而芥,假設(shè)我們希望篩選出所有專業(yè)為計(jì)算機(jī)科學(xué)的學(xué)生姓名,那么我們可以在filter篩選的基礎(chǔ)之上诽嘉,通過(guò)map將學(xué)生實(shí)體映射成為學(xué)生姓名字符串蔚出,具體實(shí)現(xiàn)如下

List<String> names = students.stream()
                            .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                            .map(Student::getName).collect(Collectors.toList());

除了上面這類基礎(chǔ)的map弟翘,java8還提供了mapToDouble(ToDoubleFunction<? super T> mapper)虫腋,mapToInt(ToIntFunction<? super T> mapper)mapToLong(ToLongFunction<? super T> mapper)稀余,這些映射分別返回對(duì)應(yīng)類型的流悦冀,Java8為這些流設(shè)定了一些特殊的操作,比如我們希望計(jì)算所有專業(yè)為計(jì)算機(jī)科學(xué)學(xué)生的年齡之和睛琳,那么我們可以實(shí)現(xiàn)如下:

int totalAge = students.stream()
                    .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                    .mapToInt(Student::getAge).sum();

通過(guò)將Student按照年齡直接映射為IntStream盒蟆,我們可以直接調(diào)用提供的sum()方法來(lái)達(dá)到目的,此外使用這些數(shù)值流的好處還在于可以避免jvm裝箱操作所帶來(lái)的性能消耗师骗。

2.2.2 flatMap

flatMap與map的區(qū)別在于 flatMap是將一個(gè)流中的每個(gè)值都轉(zhuǎn)成一個(gè)個(gè)流历等,然后再將這些流扁平化成為一個(gè)流 。舉例說(shuō)明辟癌,假設(shè)我們有一個(gè)字符串?dāng)?shù)組String[] strs = {"java8", "is", "easy", "to", "use"};寒屯,我們希望輸出構(gòu)成這一數(shù)組的所有非重復(fù)字符

List<String> distinctStrs = Arrays.stream(strs)
                                .map(str -> str.split(""))  // 映射成為Stream<String[]>
                                .flatMap(Arrays::stream)  // 扁平化為Stream<String>
                                .distinct()
                                .collect(Collectors.toList());

三、終端操作

終端操作是流式處理的最后一步,我們可以在終端操作中實(shí)現(xiàn)對(duì)流查找寡夹、歸約等操作处面。

3.1 查找
3.1.1 allMatch

allMatch用于檢測(cè)是否全部都滿足指定的參數(shù)行為,如果全部滿足則返回true菩掏,例如我們希望檢測(cè)是否所有的學(xué)生都已滿18周歲魂角,那么可以實(shí)現(xiàn)為

boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);
3.1.2 anyMatch

anyMatch則是檢測(cè)是否存在一個(gè)或多個(gè)滿足指定的參數(shù)行為,如果滿足則返回true智绸,例如我們希望檢測(cè)是否有來(lái)自武漢大學(xué)的學(xué)生野揪,那么可以實(shí)現(xiàn)為

boolean hasWhu = students.stream().anyMatch(student -> "武漢大學(xué)".equals(student.getSchool()));
3.1.3 noneMathch

noneMatch用于檢測(cè)是否不存在滿足指定行為的元素,如果不存在則返回true传于,例如我們希望檢測(cè)是否不存在專業(yè)為計(jì)算機(jī)科學(xué)的學(xué)生囱挑,可以實(shí)現(xiàn)如下

boolean noneCs = students.stream().noneMatch(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()));
3.1.4 findFirst

findFirst用于返回滿足條件的第一個(gè)元素,比如我們希望選出專業(yè)為土木工程的排在第一個(gè)學(xué)生沼溜,那么可以實(shí)現(xiàn)如下

Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();

findFirst不攜帶參數(shù)平挑,具體的查找條件可以通過(guò)filter設(shè)置,findFirst返回的是一個(gè)Optional類型系草。

3.1.5 findAny

findAny相對(duì)于findFirst的區(qū)別在于通熄,findAny不一定返回第一個(gè),而是返回任意一個(gè)找都,比如我們希望返回任意一個(gè)專業(yè)為土木工程的學(xué)生唇辨,可以實(shí)現(xiàn)如下

Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findAny();

實(shí)際上對(duì)于順序流式處理而言,findFirst和findAny返回的結(jié)果是一樣的能耻,至于為什么會(huì)這樣設(shè)計(jì)赏枚,是因?yàn)楫?dāng)我們啟用并行流式處理的時(shí)候,查找第一個(gè)元素往往會(huì)有很多限制晓猛,如果不是特別需求饿幅,在并行流式處理中使用findAny的性能要比f(wàn)indFirst好。

3.2 歸約

前面的例子中我們大部分都是通過(guò)collect(Collectors.toList())對(duì)數(shù)據(jù)封裝返回戒职,如我的目標(biāo)不是返回一個(gè)新的集合栗恩,而是希望對(duì)經(jīng)過(guò)參數(shù)化操作后的集合進(jìn)行進(jìn)一步的運(yùn)算,那么我們可用對(duì)集合實(shí)施歸約操作洪燥。Java8的流式處理提供了reduce方法來(lái)達(dá)到這一目的磕秤。

前面我們通過(guò)mapToIntStream<Student>映射成為IntStream,并通過(guò)IntStreamsum方法求得所有學(xué)生的年齡之和捧韵,實(shí)際上我們通過(guò)歸約操作市咆,也可以達(dá)到這一目的,實(shí)現(xiàn)如下

// 前面例子中的方法
int totalAge = students.stream()
                .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                .mapToInt(Student::getAge).sum();
// 歸約操作
int totalAge = students.stream()
                .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, (a, b) -> a + b);

// 進(jìn)一步簡(jiǎn)化
int totalAge2 = students.stream()
                .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, Integer::sum);

// 采用無(wú)初始值的重載版本再来,需要注意返回Optional
Optional<Integer> totalAge = students.stream()
                .filter(student -> "計(jì)算機(jī)科學(xué)".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(Integer::sum);  // 去掉初始值
3.3 收集

前面利用collect(Collectors.toList())是一個(gè)簡(jiǎn)單的收集操作蒙兰,是對(duì)處理結(jié)果的封裝,對(duì)應(yīng)的還有toSettoMap癞己,以滿足我們對(duì)于結(jié)果組織的需求膀斋。這些方法均來(lái)自于java.util.stream.Collectors,我們可以稱之為收集器痹雅。

3.3.1 歸約

收集器也提供了相應(yīng)的歸約操作仰担,但是與reduce在內(nèi)部實(shí)現(xiàn)上是有區(qū)別的,收集器更加適用于可變?nèi)萜魃系臍w約操作绩社,這些收集器廣義上均基于Collectors.reducing()實(shí)現(xiàn)摔蓝。

例1:求學(xué)生的總?cè)藬?shù)

long count = students.stream().collect(Collectors.counting());

// 進(jìn)一步簡(jiǎn)化
long count = students.stream().count();

例2:求年齡的最大值和最小值

// 求最大年齡
Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));

// 進(jìn)一步簡(jiǎn)化
Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));

// 求最小年齡
Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));

例3:求年齡總和

int totalAge4 = students.stream().collect(Collectors.summingInt(Student::getAge));

對(duì)應(yīng)的還有summingLong、summingDouble愉耙。

例4:求年齡的平均值

double avgAge = students.stream().collect(Collectors.averagingInt(Student::getAge));

對(duì)應(yīng)的還有averagingLong贮尉、averagingDouble。

例5:一次性得到元素個(gè)數(shù)朴沿、總和猜谚、均值、最大值赌渣、最小值

IntSummaryStatistics statistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));

輸出:

IntSummaryStatistics{count=10, sum=220, min=20, average=22.000000, max=24}

對(duì)應(yīng)的還有summarizingLong魏铅、summarizingDouble。

例6:字符串拼接

String names = students.stream().map(Student::getName).collect(Collectors.joining());
// 輸出:孔明伯約玄德云長(zhǎng)翼德元直奉孝仲謀魯肅丁奉

String names = students.stream().map(Student::getName).collect(Collectors.joining(", "));
// 輸出:孔明, 伯約, 玄德, 云長(zhǎng), 翼德, 元直, 奉孝, 仲謀, 魯肅, 丁奉
3.3.2 分組

在數(shù)據(jù)庫(kù)操作中坚芜,我們可以通過(guò)GROUP BY關(guān)鍵字對(duì)查詢到的數(shù)據(jù)進(jìn)行分組览芳,java8的流式處理也為我們提供了這樣的功能Collectors.groupingBy來(lái)操作集合。比如我們可以按學(xué)校對(duì)上面的學(xué)生進(jìn)行分組

Map<String, List<Student>> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool));

groupingBy接收一個(gè)分類器Function<? super T, ? extends K> classifier鸿竖,我們可以自定義分類器來(lái)實(shí)現(xiàn)需要的分類效果沧竟。上面演示的是一級(jí)分組,我們還可以定義多個(gè)分類器實(shí)現(xiàn) 多級(jí)分組缚忧,比如我們希望在按學(xué)校分組的基礎(chǔ)之上再按照專業(yè)進(jìn)行分組悟泵,實(shí)現(xiàn)如下

Map<String, Map<String, List<Student>>> groups2 = students.stream().collect(
                Collectors.groupingBy(Student::getSchool,  // 一級(jí)分組,按學(xué)校
                Collectors.groupingBy(Student::getMajor)));  // 二級(jí)分組搔谴,按專業(yè)

實(shí)際上在groupingBy的第二個(gè)參數(shù)不是只能傳遞groupingBy魁袜,還可以傳遞任意Collector類型桩撮,比如我們可以傳遞一個(gè)Collector.counting敦第,用以統(tǒng)計(jì)每個(gè)組的個(gè)數(shù)

Map<String, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));

如果我們不添加第二個(gè)參數(shù),則編譯器會(huì)默認(rèn)幫我們添加一個(gè)Collectors.toList()店量。

3.3.3 分區(qū)

分區(qū)可以看做是分組的一種特殊情況芜果,在分區(qū)中key只有兩種情況:truefalse,目的是將待分區(qū)集合按照條件一分為二融师,Java8的流式處理利用Collectors.partitioningBy()方法實(shí)現(xiàn)分區(qū)右钾,該方法接收一個(gè)謂詞,例如我們希望將學(xué)生分為武大學(xué)生和非武大學(xué)生,那么可以實(shí)現(xiàn)如下

Map<Boolean, List<Student>> partition = students.stream().collect(Collectors.partitioningBy(student -> "武漢大學(xué)".equals(student.getSchool())));

分區(qū)相對(duì)分組的優(yōu)勢(shì)在于舀射,我們可以同時(shí)得到兩類結(jié)果窘茁,在一些應(yīng)用場(chǎng)景下可以一步得到我們需要的所有結(jié)果,比如將數(shù)組分為奇數(shù)和偶數(shù)脆烟。以上介紹的所有收集器均實(shí)現(xiàn)自接口java.util.stream.Collector,該接口的定義如下山林。

public interface Collector<T, A, R> {
    /**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    Supplier<A> supplier();

    /**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    BiConsumer<A, T> accumulator();

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    BinaryOperator<A> combiner();

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *
     * <p>If the characteristic {@code IDENTITY_TRANSFORM} is
     * set, this function may be presumed to be an identity transform with an
     * unchecked cast from {@code A} to {@code R}.
     *
     * @return a function which transforms the intermediate result to the final
     * result
     */
    Function<A, R> finisher();

    /**
     * Returns a {@code Set} of {@code Collector.Characteristics} indicating
     * the characteristics of this Collector.  This set should be immutable.
     *
     * @return an immutable set of collector characteristics
     */
    Set<Characteristics> characteristics();

}

我們也可以實(shí)現(xiàn)該接口來(lái)定義自己的收集器。

四邢羔、并行流式數(shù)據(jù)處理

流式處理中的很多都適合采用分而治之的思想驼抹,從而在處理集合較大時(shí),極大的提高代碼的性能拜鹤,Java8的設(shè)計(jì)者也看到了這一點(diǎn)框冀,所以提供了并行流式處理。上面的例子中我們都是調(diào)用stream()方法來(lái)啟動(dòng)流式處理敏簿,Java8還提供了parallelStream()來(lái)啟動(dòng)并行流式處理明也,parallelStream()本質(zhì)上基于Java7的Fork-Join框架實(shí)現(xiàn),其默認(rèn)的線程數(shù)為宿主機(jī)的內(nèi)核數(shù)惯裕。

啟動(dòng)并行流式處理雖然簡(jiǎn)單诡右,只需要將stream()替換成parallelStream()即可,但既然是并行轻猖,就會(huì)涉及到多線程安全問(wèn)題帆吻,所以在啟用之前要先確認(rèn)并行是否值得(并行的效率不一定高于順序執(zhí)行),另外就是要保證線程安全咙边。此兩項(xiàng)無(wú)法保證猜煮,那么并行毫無(wú)意義,畢竟結(jié)果比速度更加重要败许,以后有時(shí)間再來(lái)詳細(xì)分析一下并行流式數(shù)據(jù)處理的具體實(shí)現(xiàn)和最佳實(shí)踐王带。

五、Stream性能問(wèn)題

參考資料:Java8 中用法優(yōu)雅的 Stream市殷,性能也“優(yōu)雅”嗎愕撰?

結(jié)論:

  • 對(duì)于簡(jiǎn)單操作,比如最簡(jiǎn)單的遍歷醋寝,Stream串行API性能明顯差于顯示迭代搞挣,但并行的Stream API能夠發(fā)揮多核特性。

  • 對(duì)于復(fù)雜操作音羞,Stream串行API性能可以和手動(dòng)實(shí)現(xiàn)的效果匹敵囱桨,在并行執(zhí)行時(shí)Stream API效果遠(yuǎn)超手動(dòng)實(shí)現(xiàn)。

所以嗅绰,如果出于性能考慮舍肠,

  • 對(duì)于簡(jiǎn)單操作推薦使用外部迭代手動(dòng)實(shí)現(xiàn)
  • 對(duì)于復(fù)雜操作搀继,推薦使用Stream API,
  • 在多核情況下翠语,推薦使用并行Stream API來(lái)發(fā)揮多核優(yōu)勢(shì)
  • 單核情況下不建議使用并行Stream API

如果出于代碼簡(jiǎn)潔性考慮叽躯,使用Stream API能夠?qū)懗龈痰拇a。即使是從性能方面說(shuō)肌括,盡可能的使用Stream API也另外一個(gè)優(yōu)勢(shì)险毁,那就是只要Java Stream類庫(kù)做了升級(jí)優(yōu)化,代碼不用做任何修改就能享受到升級(jí)帶來(lái)的好處们童。

資料來(lái)源
菜鳥(niǎo)教程 Java 8 Stream
Java8 新特性之流式數(shù)據(jù)處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畔况,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慧库,更是在濱河造成了極大的恐慌跷跪,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐板,死亡現(xiàn)場(chǎng)離奇詭異吵瞻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)甘磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門橡羞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人济舆,你說(shuō)我怎么就攤上這事卿泽。” “怎么了滋觉?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵签夭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我椎侠,道長(zhǎng)第租,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任我纪,我火速辦了婚禮慎宾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浅悉。我一直安慰自己趟据,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布仇冯。 她就那樣靜靜地躺著之宿,像睡著了一般族操。 火紅的嫁衣襯著肌膚如雪苛坚。 梳的紋絲不亂的頭發(fā)上比被,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音泼舱,去河邊找鬼等缀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娇昙,可吹牛的內(nèi)容都是我干的尺迂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冒掌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼噪裕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起股毫,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤膳音,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后铃诬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體祭陷,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年趣席,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兵志。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宣肚,死狀恐怖想罕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霉涨,我是刑警寧澤弧呐,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站嵌纲,受9級(jí)特大地震影響俘枫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逮走,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一鸠蚪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧师溅,春花似錦茅信、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至窿锉,卻和暖如春酌摇,著一層夾襖步出監(jiān)牢的瞬間膝舅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工窑多, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仍稀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓埂息,卻偏偏與公主長(zhǎng)得像技潘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子千康,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文翻譯自The Java 8 Stream API Tutorial 1. 簡(jiǎn)介 ??本教程志在細(xì)致入微享幽、深入底...
    Yodes閱讀 2,461評(píng)論 3 8
  • 1. 前言 Lambda 是JAVA8中最為重要的改變之一, 而Stream API(java.util.stre...
    Equals__閱讀 283評(píng)論 0 1
  • 什么是Stream流式計(jì)算 大數(shù)據(jù):存儲(chǔ) + 計(jì)算 集合、MySQL 本質(zhì)就是存儲(chǔ)東西的拾弃; 計(jì)算都應(yīng)該交給流來(lái)操作...
    徒手說(shuō)夢(mèng)話閱讀 3,371評(píng)論 0 0
  • 一琉闪、簡(jiǎn)介 Java 8 中的 Stream API 是對(duì)集合(Collection)對(duì)象功能的增強(qiáng)。它提供了一種高...
    一只浩子閱讀 414評(píng)論 0 1
  • Java8之Stream流(二)關(guān)鍵知識(shí)點(diǎn) Java8之Stream流(三)縮減操作 Java8之Stream...
    揭光智閱讀 9,773評(píng)論 5 31