java8(三)什麼是流?函數式數據處理神器 一、java7與java8中實現方式對比 二、如何理解流? 三、流和集合有哪些不同? 四、流的操作

流是Java API的新成員,以聲明的方式處理數據合集。就現在來說,你可以把它們看成遍歷數據集的高級迭代器。可以透明地並行處理,你無需寫任何多線程代碼了。

一、java7與java8中實現方式對比

我們首先舉個例子,看一下在java7和java8當中,引入Stream Api後,究竟發生了哪些變化?

實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能:

1.1 java7實現

import lombok.Data;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * @description: 實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能
 * @author:weirx
 * @date:2021/10/20 15:00
 * @version:3.0
 */
public class ChoiceCalorieByJava7 {

    @Data
    static class Dish {
        private String name;
        private Integer calorie;

        public Dish(String name, Integer calorie) {
            this.name = name;
            this.calorie = calorie;
        }
    }

    public static void main(String[] args) {
        List<Dish> dishes = Arrays.asList(
                new Dish("燒烤", 1000),
                new Dish("沙拉", 100),
                new Dish("漢堡", 1200),
                new Dish("火鍋", 800),
                new Dish("粥", 300)
        );

        List<Dish> lowerCalorieDishes = new ArrayList<>();
        // 篩選小於500卡路里
        for (Dish dish : dishes) {
            if (dish.getCalorie() < 500) {
                lowerCalorieDishes.add(dish);
            }
        }
        // 排序
        lowerCalorieDishes.sort(new Comparator<Dish>() {
            @Override
            public int compare(Dish o1, Dish o2) {
                return o1.getCalorie().compareTo(o2.getCalorie());
            }
        });
        List<String> dishNames = new ArrayList<>();
        // 獲取菜餚名稱
        for (Dish dish : lowerCalorieDishes) {
            dishNames.add(dish.getName());
        }
        // 打印結果 [沙拉, 粥]
        System.out.println(dishNames);
    }
}

1.2 java8實現

import lombok.Data;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description: 實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能
 * @author:weirx
 * @date:2021/10/20 15:00
 * @version:3.0
 */
public class ChoiceCalorieByJava8 {

    @Data
    static class Dish {
        private String name;
        private Integer calorie;

        public Dish(String name, Integer calorie) {
            this.name = name;
            this.calorie = calorie;
        }
    }

    public static void main(String[] args) {
        List<Dish> dishes = Arrays.asList(
                new Dish("燒烤", 1000),
                new Dish("沙拉", 100),
                new Dish("漢堡", 1200),
                new Dish("火鍋", 800),
                new Dish("粥", 300)
        );

        List<String> dishNames = dishes.stream()
                .filter(dish -> dish.getCalorie() < 500) //篩選卡路里小於500
                .sorted(Comparator.comparing(Dish::getCalorie)) //排序
                .map(Dish::getName) //獲取名稱
                .collect(Collectors.toList()); //轉成list
        // 打印結果 [沙拉, 粥]
        System.out.println(dishNames);
    }
}

相比之下在java7中繁雜的業務代碼只需要一條流式的代碼就完成了,並且將其中的stream()替換成parallelStream()就可以利用多核去執行這行代碼。

1.3 總結

下面通過上面的例子,簡單總結下使用Stream API的好處:

1)聲明性:更簡潔,更易讀。體現事物本身,而非代碼過程。
2)可複合:更靈活,減少大量的重複代碼。
3)可並行:更好的性能。

二、如何理解流?

簡短的定義就是:從支持數據處理操作的源生成的元素序列

下面簡單介紹一下這句話的含義:

1)元素序列:像集合一樣,流也提供一個接口,可以訪問特定元素的一組有序值。集合(ArrayList、LinkedList等)關注數據,而流關注的是計算(filter 、 sorted 和 map等)。

2)源:流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。

3)數據處理操作:流的數據處理功能支持類似於數據庫的操作,以及函數式編程語言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以順序執行,也可並行執行。

除此之外還有以下兩個特點:

4)流水線:多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線。流水線的操作可以看作對數據源進行數據庫式查詢。

5)內部迭代:與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。

結合前面的例子簡單說明下:

        List<String> dishNames = dishes.stream()
                .filter(dish -> dish.getCalorie() < 500) //篩選卡路里小於500
                .sorted(Comparator.comparing(Dish::getCalorie)) //排序
                .map(Dish::getName) //獲取名稱
                .collect(Collectors.toList()); //轉成list

其中dishNames就是源,它給流提供一個元素序列,接下來,對流應用一系列數據處理操作:filter 、 sorted 和 map等。最後, collect 操作開始處理流水線,並返回結果(它和別的操作不一樣,因爲它返回的不是流,在這裏是一個 List。

三、流和集合有哪些不同?

1)集合可以多次遍歷,而流只能被遍歷一次

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Stream<Integer> stream = list.stream();
        stream.forEach(System.out::print);
        stream.forEach(System.out::print);
    }

如上操作會報出以下異常:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.cloud.bssp.java8.stream.CollectionAndStream.main(CollectionAndStream.java:19)

2)遍歷數據的方式不同
使用 Collection 接口需要用戶去做迭代(比如用 foreach ),這稱爲外部迭代。 相反,Streams庫使用內部迭代

相比於外部迭代,內部迭代的優勢在於:
內部迭代時,項目可以透明地並行處理,或者用更優化的順序進行處理。不需要像外部迭代手動去處理並行問題。

四、流的操作

Stream API定義了很多的操作,這些操作可以分爲兩大類:

1)filter、map、limit等可以連成一條流水線。這類操作也可以簡稱爲中間操作。

諸如 filter 或 sorted 等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理。這是因爲中間操作一般都可以合併起來,在終端操作時一次性全部處理。

2)collect觸發流水線執行並關閉它。這類操作被稱爲終端操作。

終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如 List 、 Integer ,甚至 void 。

總而言之,流的使用一般包括三件事:
1)一個數據源(如集合)來執行一個查詢;
2)一箇中間操作鏈,形成一條流的流水線;
3)一個終端操作,執行流水線,並能生成結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章