java8 Stream經常使用方法和特性淺析

前言:對大數據量的集合的循環處理,stream擁有極大的優點,徹底能夠用stream去代替for循環。html

Stream介紹

  先說下Stream的優點:它是java對集合操做的優化,相較於迭代器,使用Stream的速度很是快,而且它支持並行方式處理集合中的數據,默認狀況能充分利用cpu的資源。同時支持函數式編程,代碼很是簡潔。java

  Stream是一種用來計算數據的流,它自己並無存儲數據。你能夠認爲它是對數據源的一個映射或者視圖。sql

  它的工做流程是:獲取數據源->進行一次或屢次邏輯轉換操做->進行歸約操做造成新的流(最後能夠將流轉換成集合)。編程

1.生成流

Stream的建立須要一個數據源(一般是一個容器或者數組):api

例1:Stream<String> stream = Stream.of("I", "got", "you", "too");數組

例2:String [] strArray = new String[] {"a", "b", "c"};app

  stream = Arrays.stream(strArray);ide

例3:List<String> list = Arrays.asList(strArray);函數式編程

  stream = list.stream();函數

2.流的操做

流的操做類型分2種:中間操做與聚合操做

2.1中間操做(intermediate ):

中間操做就是對容器的處理過程,包括:排序(sorted...),篩選(filter,limit,distinct...),映射(map,flatMap...)等

 

2.1.1 排序操做(sorted):(參考:http://www.javashuo.com/article/p-xcyazmue-bg.html

sorted提供了2個接口:

一、 sorted() 默認使用天然序排序, 其中的元素必須實現 Comparable 接口 。
二、 sorted(Comparator<? super T> comparator) :咱們可使用lambada 來建立一個 Comparator 實例。能夠按照升序或着降序來排序元素。 
注意sorted是一個有狀態的中間操做,即,只有所有執行完全部的數據才能知道結果。

好比:將一些字符串在地址中按出現的順序排列:

          String address = "中山北路南京大學仙林校區";
		List<String> aList = new ArrayList<>();
		aList.add("南京");
		aList.add("大學");
		aList.add("仙林校區");
		aList.add("仙林大學城");
		aList.add("中山北路");
		aList.stream().sorted(
			Comparator.comparing(a->address.indexOf(a))
          ).forEach(System.out :: println);

也能夠像下面這樣不使用比較器:

		aList.stream().sorted(
				(a,b)->address.IndexOf(a)-address.IndexOf(b)
		).forEach(System.out :: println);//由大到小排序

輸出結果:

注:1.這裏仙林大學城這個字段沒有出現,因此序號是-1,被排在最前面。

  2.Comparator.comparing();這個是比較器提供的一個方法,它返回的也是一個比較器,源碼以下:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

2.1.2 篩選操做(filter)

上一步中,咱們把一些字符串,按照在地址中出現的順序排序。

接下來咱們可能想要進行篩選,把不在地址中,可是indexof爲「-1」,排在最前面的數據篩選掉:

filter能夠對集合進行篩選,它的參數能夠是一個lambda表達式,流中的數據將會經過該lambda表達式返回新的流。

這裏Stream有一個特性很重要,它像一個管道,能夠將多個操做鏈接起來,並只執行一次for循環,這樣大大提升了效率,即便第二次的流操做須要第一次流操做的結果,時間複雜度也只有一個for循環:

因而我能夠在前面加個filter(),這樣把「-1」過濾掉:

String address = "中山北路南京大學仙林校區";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大學");
aList.add("仙林校區");
aList.add("仙林大學城");
aList.add("中山北路");
aList.stream().filter(a->address.indexOf(a)!=-1)
.sorted(
  Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);

輸出結果:

注:foreach是一個終端操做,參數也是一個函數,它會迭代中間操做完成後的每個數據,這裏它將每一個不爲空的元素打印出來。

其它的過濾操做還包括:

limit(long maxSize):得到指定數量的流。

distinct():經過hashCode和equals去除重複元素。

 

2.1.3 映射操做(map)

映射操做,就像一個管道,能夠將流中的元素經過一個函數進行映射,返回一個新的元素。

這樣遍歷映射,最終返回一個新的容器,注意:這裏返回的新容器數據類型能夠不與原容器類型相同:

舉個例子:咱們將address中每一個元素的位置找出,並返回一個int類型的存儲位置信息的數組:

 @Test public void test() { String address = "中山北路南京大學仙林校區"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大學"); aList.add("仙林校區"); aList.add("仙林大學城"); aList.add("中山北路"); List<Integer> aIntegers =aList.stream() .map(str->mapFunc(address, str)).collect(Collectors.toList()); System.out.println(aIntegers);//.forEach(System.out :: println); 
 } private int mapFunc(String address,String str) { return address.indexOf(str); }

結果以下:

 

2.2規約操做(reduction ):

以前的中間操做只是對流中數據的處理,最終咱們仍是要將它們整合輸出爲一個結果,好比,返回一個最大值,返回一個新的數組,或者將全部元素進行分組等,這就是規約(末端)操做的做用。

咱們經常使用的末端操做函數有Reduce()和collect();

2.2.1Reduce

reduce就是減小的意思,它會將集合中的全部值根據規則計算,最後只返回一個結果。

它有三個變種,輸入參數分別是一個參數、二個參數以及三個參數;


1.一個參數的Reduce

它的參數就是一個函數接口:Optional<T> reduce(BinaryOperator<T> accumulator)

好比,咱們找出數組中長度最大的一個數:

public void test() {
        String address = "中山北路南京大學仙林校區";
        List<String> aList = new ArrayList<>();
        aList.add("南京");
        aList.add("大學");
        aList.add("仙林校區");
        aList.add("仙林大學城");
        aList.add("中山北路");
        Optional<String> a =aList.stream()
                .reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
        System.out.println(a.get());//仙林大學城
    }

這裏的Optional<T>就是一個容器,它能夠避免空指針,具體能夠百度,這裏也能夠返回一個String的。

 

2.兩個參數的Reduce

T reduce(T identity, BinaryOperator<T> accumulator)

2個參數其實除了一個函數接口之外,還包括一個固定的初始化的值,它會做爲容器的第一個元素進入計算過程:

例:將每一個字符串拼接,並在以前加上「value:」:

public void test() {
        String address = "中山北路南京大學仙林校區";
        List<String> aList = new ArrayList<>();
        aList.add("南京");
        aList.add("大學");
        aList.add("仙林校區");
        aList.add("仙林大學城");
        aList.add("中山北路");
        String t="value:";
        String a =aList.stream()
                .reduce(t, new BinaryOperator<String>() {
                    @Override
                    public String apply(String s, String s2) {
                        return s.concat(s2);
                    }
                });    
        System.out.println(a);
    }

結果以下:

3.三個參數的狀況主要是在並行(parallelStream)狀況下使用:能夠參考(https://blog.csdn.net/icarusliu/article/details/79504602),有須要能夠了解下。

 

2.2.2Collect

collect是一個很是經常使用的末端操做,它自己的參數很複雜,有3個:

<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner);

還好,考慮到咱們平常使用,java8提供了一個收集器(Collectors),它是專門爲collect方法量身打造的接口:

咱們經常使用collect將流轉換成List,Map或Set:

1.轉換成list:

Stream<String> stream = Stream.of("I", "love", "you", "too"); 
List<String> list = stream.collect(Collectors.toList());

2.轉換成Map:

咱們可使用Collector.toMap()接口:Collectors.toMap(keyMapper, valueMapper),這裏就須要咱們指定key和value分別是什麼。

例:咱們將數組中的字符串做爲key,字符串長度做爲value,生成一個map:

String address = "中山北路南京大學仙林校區";
        List<String> aList = new ArrayList<>();
        aList.add("南京");
        aList.add("大學");
        aList.add("仙林校區");
        aList.add("仙林大學城");
        aList.add("中山北路");
        String t="value:";
        
        Map<String, Integer> maps = 
                aList.stream().collect(Collectors.toMap(Function.identity(), String::length));
        System.out.println(maps);

 

打印結果:

{中山北路=4, 大學=2, 仙林大學城=5, 仙林校區=4, 南京=2}

 

一般,咱們在進行分組操做的時候也會將容器轉換爲Map,這裏也說明一下:Collectors.groupingBy(classifier)

groupingBy與sql的group by相似,就是一個分組函數,

例:咱們將數組中的字符串按長度分組:

        String address = "中山北路南京大學仙林校區";
        List<String> aList = new ArrayList<>();
        aList.add("南京");
        aList.add("大學");
        aList.add("仙林校區");
        aList.add("仙林大學城");
        aList.add("中山北路");
        String t="value:";
        
        Map<Integer, List<String>> maps = 
                aList.stream().collect(Collectors.groupingBy(String::length));
        System.out.println(maps);

打印結果:

{2=[南京, 大學], 4=[仙林校區, 中山北路], 5=[仙林大學城]}

 

其餘的末端操做api: 

 findFirst:返回第一個元素,常與orElse一塊兒用:  Stream.findFirst().orElse(null):返回第一個,若是沒有則返回null

 allMatch:檢查是否匹配全部元素:Stream.allMatch(str->str.equals("a"))

 anyMatch:檢查是否至少匹配一個元素.

 

3.Stream的特性

1. 中間操做惰性執行:一個流後面能夠跟隨0到多箇中間操做,主要目的是打開流,並無真正的去計算,而是作出某種程度的數據映射 /過濾,而後返回一個新的流,交給下一個操做使用。這類操做都是惰性化的(lazy),就是說,僅僅調用到這類方法,並無真正開始流的遍歷,並無消耗資源。
還有多箇中間操做的話,這裏的時間複雜度並非n個for循環,轉換操做都是 lazy 的, 多個轉換操做只會在 Terminal 操做的時候融合起來,一次循環完成能夠這樣簡單的理解,Stream 裏有個操做函數的集合,每次轉換操做就是把轉換函數放入這個集合中,在Terminal操做的時候循環 Stream 對應的集合,而後對每一個元素執行全部的函數。

2.流的末端操做只能有一次: 當這個操做執行後,流就被使用「光」了,沒法再被操做。因此這一定是流的最後一個操做。以後若是想要操做就必須新打開流。

 

 

關於流被關閉不能再操做的異常:

這裏曾經遇到過一個錯誤:stream has already been operated upon or closed

意思是流已經被關閉了,這是由於當咱們使用末端操做以後,流就被關閉了,沒法再次被調用,若是咱們想重複調用,只能從新打開一個新的流。

相關文章
相關標籤/搜索