java8 -函數式編程之Stream

        在 java8 -函數式編程之Lambda表達式、 java8 -函數式編程之四個基本接口 、java8 -函數式編程之Optional 三篇文章中,咱們已經對函數式編程有了充分的瞭解,接下來,咱們將會運用以前學到的知識學習項目中經常使用到的 java8 Stream 流式操做。java

什麼是Stream

        Stream API 藉助於 Lambda 表達式,極大的提升編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操做,併發模式可以充分利用多核處理器的優點,使用 fork/join 並行方式來拆分任務和加速處理過程。一般編寫並行代碼很難並且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就能夠很方便地寫出高性能的併發程序。編程

        流主要有三部分構成:獲取一個數據源(source)→ 數據轉換 → 執行操做獲取想要的結果。每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(能夠有屢次轉換),這就容許對其操做能夠像鏈條同樣排列,變成一個管道。api

    

Stream的特色

無存儲性:數組

    流不是存儲元素的數據結構;相反,它須要從數據結構,數組,生成器函數或IO管道中獲取數據並經過流水線地(計算)操做對這些數據進行轉換。數據結構

函數式編程:多線程

    Stream上操做會產生一個新結果,而不會去修改原始數據。好比filter過濾操做它只會根據原始集合中將未被過濾掉的元素生成一個新的Stream,而不是真的去刪除集合中的元素。併發

惰性求值:app

    不少Stream操做(如filter,map,distinct等)都是惰性實現,這樣作爲了優化程序的計算。好比說,要從一串數字中找到第一個能被10整除的數,程序並不須要對這一串數字中的每一個數字進行測試。流操做分爲兩種:中間操做(返回值仍爲Stream,仍可執行操做),終斷操做(結束Stream操做)。中間操做都是惰性操做。less

無限數據處理:dom

    集合的大小是有限的,可是流能夠對無限的數據執行操做。好比可使用limit或findFirst這樣的操做讓Stream操做在有限的時間內結束。

一次性消費:

    流只能使用(「消費」)一次,一旦調用終斷操做,流就不能再次使用,必須從新建立一個流。就像迭代器同樣,遍歷一遍後,想要再次遍歷須要從新建立一個迭代器。

Stream的源的構建

有多種方式能夠構建流:

(1)靜態工廠

  • Stream.of()
  • IntStream.of()
  • LongStream.of()
  • DoubleStream.of()

(2)Collection 和數組

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array) or Stream.of()

(3)字符流

  • BufferdReader.lines()

(4)文件路徑

  • Files.walk()
  • Files.lines()
  • Files.find()

(5)其它

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

生成流的時候,除了能夠生成串行流,也能夠生成並行流,即並行處理流的操做。

final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");

        //串行流
        long count1 = strings.stream()
                .filter(s -> {
                    System.out.println("thread:" + Thread.currentThread().getId());
                    return s.startsWith("a");
                })
                .count();
        System.out.println(count1);

        //並行流
        long count2 = strings.parallelStream()
                .filter(s -> {
                    System.out.println("thread:" + Thread.currentThread().getId());
                    return s.startsWith("a");
                })
                .count();
        System.out.println(count2);

Stream操做的分類

  Stream操做分類  
中間操做(Intermediate operations) 無狀態(Stateless)

unordered(), filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble(), peek();

  有狀態(Stateful) distinct();  sorted();  limit(), skip()
終斷操做(Terminal operations) 非短路操做 forEach(), forEachOrdered(); reduce(), collect(), max(), min(), count(); toArray()
  短路操做(short-circuiting) anyMatch(), allMatch(), noneMatch(); findFirst(), findAny()

中間操做:

    返回一個新的Stream。中間操做都是惰性的,它們不會對數據源執行任何操做,僅僅是建立一個新的Stream。在終斷操做執行以前,數據源的遍歷不會開始。

終斷操做:

    遍歷流並生成結果或者反作用。執行完終斷操做後,Stream就會被「消費」掉,若是想再次遍歷數據源,則必須從新建立新的Stream。大多數狀況下,終斷操做的遍歷都是即時的——在返回以前完成數據源的遍歷和處理,只有iterator()和spliterator()不是,這兩個方法用於提供額外的遍歷功能——讓開發者本身控制數據源的遍歷以實現現有Stream操做中沒法知足的操做(實際上現有的Stream操做基本能知足需求,因此這兩個方法目前用的很少)。

 

Stream的操做

中間操做

  • map

    使用傳入的Function對象對Stream中的全部元素進行處理,返回的Stream對象中的元素爲原元素處理後的結果。

//map,平方數
        List<Integer> nums = Arrays.asList(1, 2, 3, 4);
        Stream<Integer> integerStream = nums.stream().map(n -> n * n);
        Collection<Integer> squareNums = integerStream.collect(Collectors.toList());
        squareNums.forEach(integer -> System.out.println(integer));

 

  • flatMap

    map 生成的是個 1:1 映射,每一個輸入元素,都按照規則轉換成爲另一個元素。flatMap,則是一對多映射關係的。

Stream<List<Integer>> inputStream = Stream.of(
                Arrays.asList(1),
                Arrays.asList(2, 3),
                Arrays.asList(4, 5, 6)
        );
        Stream<Integer> integerStream1 = inputStream.flatMap(list -> list.stream());
        List<Integer> squareNums2 = integerStream1.map(n -> n * n).collect(Collectors.toList());
        squareNums2.forEach(integer -> System.out.println(integer));

 

  • filter

    filter 對原始 Stream 進行某項測試,經過測試的元素被留下來生成一個新 Stream

final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");
        strings.stream()
                .filter(s -> s.startsWith("a"))
                .forEach(System.out::println);

 

  • peek

        peek,遍歷Stream中的元素,和forEach相似,區別是peek不會「消費」掉Stream,而forEach會消費掉Stream;peek是中間操做因此也是惰性的,只有在Stream「消費」的時候生效。

//peek
        Stream.of("one", "two", "three", "four")
                .peek(e -> System.out.println("原來的值: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("轉換後的值: " + e))
                .collect(Collectors.toList());

 

  • limit 和 skip

    limit取頭部的數據(或者說截取前面的元素),skip取尾部的數據(跳過前面的元素)

//limit, 返回 Stream 的前面 n 個元素
        final List<String> strings = Arrays.asList("ab", "a", "abc", "b", "bc");
        strings.stream()
                .limit(3)
                .forEach(System.out::println);
        System.out.println("==============");

        //skip 則是扔掉前 n 個元素(
        strings.stream()
                .skip(3)
                .forEach(System.out::println);
        System.out.println("==============");

 

  • distinct

    去除重複的元素

Stream<String> distinctString = Stream.of("a","b","b","c")
                .distinct();//去重
        distinctString.forEach(System.out::println);

 

  • sorted

    對Stream中的元素進行排序。有兩個重載方法,其中 Stream<T> sorted() 須要元素實現了Comparable接口。

Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                .sorted()
                .forEach(System.out::println);

        Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                .sorted((o1, o2) -> {
                    return o1.compareTo(o2);
                })
                .forEach(System.out::println);

 

終端操做

短路操做

    短路操做其實就和咱們平常編程用到的&&||運算符處理過程相似,遇到一個知足條件的就當即中止判斷。

  • anyMatch

    只要其中有一個元素知足傳入的Predicate時返回True,不然返回False。前面的中間操做只要anyMatch中的條件成立後,就再也不執行。與邏輯運算符 || 相似。

//anyMatch
        boolean anyMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                .peek(s -> System.out.println(s))
                .anyMatch(s -> s.startsWith("b"));
        System.out.println(anyMatchReturn);

它的執行結果

ab
a
abc
b
true

 

  • allMatch

    全部元素均知足傳入的Predicate時返回True,不然False。只要allMatch條件有一個爲false,中間操做將終止執行。與邏輯運算符&&相似

boolean allMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                .peek(s -> System.out.println(s))
                .allMatch(s -> s.startsWith("b"));
        System.out.println(allMatchReturn);

運行結果:

ab
false

 

  • noneMatch

    全部元素均不知足傳入的Predicate時返回True,不然False。只要allMatch條件有一個爲true,中間操做將終止執行。

boolean noneMatchReturn = Arrays.asList("ab", "a", "abc", "b", "bc").stream()
                .peek(s -> System.out.println(s))
                .noneMatch(s -> s.startsWith("b"));
        System.out.println(noneMatchReturn);

結果:

ab
a
abc
b
false

 

非短路操做

  • forEach

    對全部元素進行迭代處理,無返回值

Arrays.asList("ab", "a", "abc", "b", "bc").forEach(s -> {
            System.out.println(s);
        });

 

  • reduce

    計算機術語:規約,經過累加器accumulator,對前面的序列進行累計操做,並最終返回一個值。累加器accumulator有兩個參數,第一個是前一次累加的結果,第二個是前面集合的下一個元素。經過reduce,能夠實現 average, sum, min, max, count。reduce有三個重載方法:

    (1)T reduce(T identity, BinaryOperator<T> accumulator) :這裏的identity是初始值。下面將會把幾個字符組裝成一個字符串

String concat = Stream.of("A", "B", "C", "D").reduce("H",
                (x, y) -> {
                    System.out.println("x=" + x + ", y=" + y);
                    return x.concat(y);
                });
        System.out.println(concat);

    輸出結果:

x=H, y=A
x=HA, y=B
x=HAB, y=C
x=HABC, y=D
HABCD

        

(2)Optional<T> reduce(BinaryOperator<T> accumulator):因爲沒有初始值,這裏輸出Optional類型,避免空指針

Optional<String> concat2Optional = Stream.of("A", "B", "C", "D").reduce(
                (x, y) -> {
                    System.out.println("x=" + x + ", y=" + y);
                    return x.concat(y);
                });
        System.out.println(concat2Optional.orElse("default"));

輸出結果:

x=A, y=B
x=AB, y=C
x=ABC, y=D
ABCD

 

    (3)<U> U reduce(U identity,  BiFunction<U, ? super T, U> accumulator,  BinaryOperator<U> combiner)

    這個方法很是複雜,若是感興趣能夠參考其它文章: Java8新特性學習-Stream的Reduce及Collect方法詳解

 

  • collect

    collect方法能夠經過收集器collector將流轉化爲其餘形式,好比字符串、list、set、map。collect有兩個重載方法,其中一個是最經常使用的:

    <R, A> R collect(Collector<? super T, A, R> collector)。官方爲了咱們轉換方便,已經在Collectors類中封裝了各類各樣的collector。下面看一些經常使用的收集器。

 

拼接字符串

//collect,拼接字符串
        String collect1 = Stream.of("A", "B", "C", "D")
                .collect(Collectors.joining());
        System.out.println(collect1);

轉成List

//collect,轉成arrayList
        List<String> collect2 = Stream.of("A", "B", "C", "D")
                .collect(Collectors.toList());

轉成set

Set<String> collect3 = Stream.of("A", "B", "C", "D")
                .collect(Collectors.toSet());

 

轉成map

Collectors的toMap方法簽名以下所示,前一個mapper轉換成map中的key,後一個mapper轉換成map中的value

Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper)

Map<String, String> collect3 = Stream.of("A", "B", "C", "D")
                .collect(Collectors.toMap(
                        s -> s,
                        s -> s
                ));

 

相關資料

Java 8 函數式編程系列

    java8 -函數式編程之Lambda表達式

    java8 -函數式編程之四個基本接口

    java8 -函數式編程之Optional

    java8 -函數式編程之Stream

 

參考資料:

Java函數式編程與Lambda表達式

相關文章
相關標籤/搜索