淺談java8中的流的使用

咱們在開發的過程當中會大量的使用集合,集合能夠將數據進行分組,處理,好多的處理數據的業務邏輯相似於數據庫的操做,好比說對一系列的實體根據它其中的某個屬性來分組,篩選,像這樣的操做,數據庫是容許你聲明式的指定這些操做的。好比說:java

SELECT name FROM apple WHERE weight < 400;
複製代碼

這樣的業務邏輯,咱們以前的代碼實現都是for循環裏面,填上一大堆的if判斷,新建的臨時變量,佔用的代碼空間很大,並且可讀性也很差。數據庫

List<Apple> appleList = new ArrayList<>();
        List<Apple> wantedAppleList = new ArrayList<>();
        for (Apple app : appleList) {
            if (app.getWeight() < 400) {   //篩選
                wantedAppleList.add(app);
            }
        }

        Collections.sort(wantedAppleList, new Comparator<Apple>() {
            public int compare(Apple d1, Apple d2) {  //排序
                return Long.compare(d1.getWeight(), d2.getWeight());
            }
        });

        List<Long> appleIdList = new ArrayList<>();
        for (Apple d : wantedAppleList) {
            appleIdList.add(d.getId());   //獲取實體id
        }
複製代碼

看上面的代碼,佔用的空間很大,並且還會產生和使用垃圾變量,好比代碼中的appleIdList,它起到的做用只是一個一次性的中間容器。 在java8以後,這樣的語句能夠不讓它出現了,你不須要擔憂怎麼去顯式的實現如何篩選,你只須要說明你想要什麼就好了。 若是要處理大量的元素,提升性能,你須要並行處理,利用多核架構,可是寫並行代碼更復雜,而調試起來也比較難受。好比說,使用synchronized來編寫代碼,這個代碼是迫使代碼順序執行,也就違背了並行執行的初衷,這個在多核cpu上執行所需的成本會更大,多核的cpu的每一個處理器內核都有本身獨立高速緩存,加鎖須要把這些同步緩存同步進行,須要在內核間進行緩慢的緩存一致性協議通訊。 痛點說完了,接下來咱們說下java8中的流是怎麼使用的。設計模式

java8中的流

java8中的集合支持一個新的Stream方法,它會返回一個流,到底什麼是流呢? 流:從支持數據處理的源生成的元素序列。讓咱們來咬文嚼字的來分別解釋下,數組

  • 元素序列:和集合同樣,流也提供了一個接口,能夠訪問特定元素類型的一組有序值,集合是一種數據結構,它的目的是存儲和訪問,可是流的目的是表達計算。
  • 源:固然就是提供數據的源頭,大部分是集合、數組,由有序列表的產生的流順序也是一致的。
  • 數據處理操做:流的數據處理功能很是相似於數據庫的操做,就是表達出來你要怎麼處理。

流也有兩個重要的特色:瀏覽器

  • 流水線:Stream 中的不少操做會返回一個流,這樣操做就能夠連接起來,造成一個大的流水線。
  • 內部迭代:與集合自己的顯式迭代不一樣,Stream流的操做都是在背後進行的。 針對開頭的代碼,若是是java8的方式咱們應該怎麼寫呢?
//若是是多核架構的話,能夠將stream()換成parallelStream()
List<Long> appleIdList = appleList
                .stream()
            //  .parallelStream()   並行處理
                .filter(apple -> apple.getWeight() < 400)
                .sorted(Comparator.comparing(Apple::getWeight))
                .map(Apple::getId)
                .collect(Collectors.toList());
複製代碼

通過對比,思考,咱們能夠發現,後者的代碼是以聲明的方式寫的,就是陳述了你想要作什麼,而不是一大堆的if、for的去實現。這樣咱們再遇到別的需求的時候,不用再去複製代碼了,你只要再按照這樣的方式去陳述下你想要的就能夠了,你能夠把幾個簡單操做連接起來,來造成一個流水線,就表達複雜的數據處理。緩存

集合和流的內在區別

那麼集合和流的內在區別是什麼呢?bash

  1. 比較粗略的說,二者的主要區別就是在於什麼時間進行計算。 集合是一個內存中的數據結構,它存儲包含着全部的值,每個元素都是存放在內存裏的,元素的計算結果才能成爲集合的一部分。 而流呢,是概念上的固定的數據結構,元素都是按需計算的。從另外一個角度來講,流就是延遲建立的集合,只要在須要的時候纔會計算值,獲得結果。套用管理學上的話:需求驅動,實時創造。 舉一個例子:用瀏覽器進行搜索,當你輸入一個關鍵字的時候,Google不會在全部的匹配結果都出來,全部的圖片和都下載好以後才返回給你,而是首先給你10個或是20個,當你點擊下一頁的時候再給你接下來的10個,20個。這也就是隻有在須要的時候纔會去計算,好像有點相似於懶加載的意思。 有一點流和迭代器比較相似,就是流只能遍歷一次,若是你還想在處理一遍,就只能從原始的數據源那裏從新生成一個流來遍歷(固然了,這裏說的集合,不是I/O流)。數據結構

  2. 另外一個關鍵的區別就是二者的遍歷數據的方式。 Collection接口須要用戶去作迭代,for-each,就是外部迭代,去顯式的取出每一個元素,去處理。而Stream使用的是內部迭代它把迭代已經作了,還把獲得的流存儲起來。內部迭代的時候,項目能夠透明的並行處理,或者是用更好的順序去處理,Stream庫的內部迭代能夠本身去選擇一種適合你硬件的數據表示和並行實現。架構

流的操做:咱們再來看下上面的代碼:filter、sorted、map流水線式的稱爲中間操做。collect觸發流水線操做的是終端操做。app

流的使用

流的使用包括三件事:

  • 數據源,集合
  • 中間操做,流水線
  • 終端操做,執行流水線,生成結果

其實流水線的背後理念相似於構建器模式,構建器模式就是用來設置一套配置,也就是這裏的中間操做,接着調用built方法,也就是這裏的終端操做。關於設計模式,這裏就不細說了,之後也會專門的說下各個設計模式,各位小夥伴不要捉急。

篩選 filter:篩選出符合條件的 distinct:去除重複 limit:返回一個不超過給定長度的流,截短 skip:跳過給定長度,若是超過總量,返回空

List<Apple> red = appleList.stream().filter(apple -> apple.getColor().equals("red")).distinct().limit(3).collect(Collectors.toList());
複製代碼

map:對流的元素應用函數,接受一個函數做爲參數,而且會把這個函數應用到每個元素上,並映射到一個新的元素。

List<String> appleNameList = appleList.stream().map(Apple::getName).collect(Collectors.toList());
複製代碼

有的時候也會有這樣狀況,在使用map操做以後,會產生一個集合或者是數組,而你須要把全部的集合合併爲一個集合,這被叫作流的扁平化,接着上代碼:

List<String> collect = appleList.stream().map(Apple::getName).map(word -> word.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
複製代碼

flatMap()方法讓你把流中的每個值都換成另外一個流,而後把全部的流鏈接起來,成爲一個流。

查找和匹配 anyMatch:流中是否有一個元素符合 allMatch:流中元素是否所有符合 noneMatch:流中無元素符合條件 findAny:查找當前流中的任意元素

Optional<String> any = appleList.stream().map(Apple::getName).map(word -> word.split("")).flatMap(Arrays::stream).distinct().findAny();
複製代碼

Optional是一個容器,表明一個值存在值或不存在,這樣就避免出現null了。能夠用isPresent()方法判斷這個容器是否有值。

歸約:

//源碼中的reduce
T reduce(T identity, BinaryOperator<T> accumulator);

Integer allSum = numbers.stream().reduce(0, (a, b) -> a + b);
複製代碼

reduce()方法有兩個參數,總和變量的初始值,上面的0就是,後面的Lambda是加和的操做。你也能夠操做相乘,Lambda反覆的結合每一個元素,一直到流被規約成一個值。 java8中Integer類如今有了一個靜態的sum方法來求和,你還能夠這麼寫:

Integer allInteger = numbers.stream().reduce(0, Integer::sum);
複製代碼

關於reduce,它還有一個重載的變體,看下面,沒有初始值,返回一個Optional對象,大家知道爲何會是Optional嗎?由於沒有初始值,加和操做可能不會獲得值。

Optional<Integer> result = numbers.stream().reduce(Integer::sum);
Optional<Integer> maxResult = numbers.stream().reduce(Integer::max);
Optional<Integer> minResult = numbers.stream().reduce(Integer::min);
複製代碼

最後

若是對本文有任何異議或者說有什麼好的建議,能夠加我好友(公衆號後臺聯繫做者),也能夠在下面留言區留言。但願這篇文章能幫助你們披荊斬棘,乘風破浪。

這樣的分享我會一直持續,你的關注、轉發和好看是對我最大的支持,感謝。

相關文章
相關標籤/搜索