JDK1.8 之Stream API總結

Stream是 Java 8新增長的類,用來補充集合類。java

Stream表明數據流,流中的數據元素的數量多是有限的,也多是無限的。git

Stream和其它集合類的區別在於:其它集合類主要關注與有限數量的數據的訪問和有效管理(增刪改),而Stream並沒有提供訪問和管理元素的方式,而是經過聲明數據源的方式,利用可計算的操做在數據源上執行,固然BaseStream.iterator() 和 BaseStream.spliterator()操做提供了遍歷元素的方法。github

Java Stream提供了提供了串行和並行兩種類型的流,保持一致的接口,提供函數式編程方式,以管道方式提供中間操做和最終執行操做,爲Java語言的集合提供了現代語言提供的相似的高階函數操做,簡化和提升了Java集合的功能。編程

本文首先介紹Java Stream的特色,而後按照功能分類逐個介紹流的中間操做和終點操做,最後會介紹第三方爲Java Stream作的擴展。數組

介紹

Stream接口還包含幾個基本類型的子接口如IntStream, LongStream 和 DoubleStream。安全

關於流和其它集合具體的區別,能夠參照下面的列表:網絡

  1. 不存儲數據。流是基於數據源的對象,它自己不存儲數據元素,而是經過管道將數據源的元素傳遞給操做。
  2. 函數式編程。流的操做不會修改數據源,例如filter不會將數據源中的數據刪除。
  3. 延遲操做。流的不少操做如filter,map等中間操做是延遲執行的,只有到終點操做纔會將操做順序執行。
  4. 能夠解綁。對於無限數量的流,有些操做是能夠在有限的時間完成的,好比limit(n) 或 findFirst(),這些操做但是實現"短路"(Short-circuiting),訪問到有限的元素後就能夠返回。
  5. 純消費。流的元素只能訪問一次,相似Iterator,操做沒有回頭路,若是你想從頭從新訪問流的元素,對不起,你得從新生成一個新的流。

流的操做是以管道的方式串起來的。流管道包含一個數據源,接着包含零到N箇中間操做,最後以一個終點操做結束。併發

並行 Parallelism

全部的流操做均可以串行執行或者並行執行。除非顯示地建立並行流,不然Java庫中建立的都是串行流。 Collection.stream()爲集合建立串行流而Collection.parallelStream()爲集合建立並行流。IntStream.range(int, int)建立的是串行流。經過parallel()方法能夠將串行流轉換成並行流,sequential()方法將流轉換成串行流。app

通常,除非方法的Javadoc中指明瞭方法在並行執行的時候結果是不肯定(好比findAny、forEach),不然串行和並行執行的結果應該是同樣的。less

Non-interference

流能夠從非線程安全的集合中建立,當流的管道執行的時候,非concurrent數據源不該該被改變。下面的代碼會拋出java.util.ConcurrentModificationException異常:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));

在設置中間操做的時候,能夠更改數據源,只有在執行終點操做的時候,纔有可能出現併發問題(拋出異常,或者不指望的結果),好比下面的代碼不會拋出異常(而且結果中包含新加入的數據):

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
sl.forEach(System.out::println);

此處的中間操做指的是在流操做開始前的操做。 流對象對數據源的操做在一條語句中操做完成,完成後可轉爲結果或者轉爲其餘流的數據源,可是該流對象再繼續操做(至關於一個流對象開始有數據源,後來數據流轉給別的流,本身沒有數據源,不能操做原數據源了)。

對於concurrent數據源,不會有這樣的問題,好比下面的代碼很正常:

List<String> l = new CopyOnWriteArrayList<>(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));

雖然咱們上面例子是在終點操做中對非併發數據源進行修改,可是非併發數據源也可能在其它線程中修改,一樣會有併發問題

無狀態 Stateless behaviors

大部分流的操做的參數都是函數式接口,可使用Lambda表達式實現。它們用來描述用戶的行爲,稱之爲行爲參數(behavioral parameters)。

若是這些行爲參數有狀態,則流的操做的結果多是不肯定的,好比下面的代碼:

List<String> l = new ArrayList(Arrays.asList("one", "two", ……));
class State {
    boolean s;
}
final State state = new State();
Stream<String> sl = l.stream().map(e -> {
    if (state.s)
        return "OK";
    else {
        state.s = true;
        return e;
    } 
});
sl.forEach(System.out::println);

上面的代碼在並行執行時屢次的執行結果多是不一樣的。這是由於這個lambda表達式是有狀態的。

反作用 Side-effects

有反作用的行爲參數是被鼓勵使用的。

反作用指的是行爲參數在執行的時候有輸入輸入,好比網絡輸入輸出等。

行爲參數指的是stream的操做函數中的lambda參數,例如filter方法中的參數。

這是由於Java不保證這些反作用對其它線程可見,也不保證相同流管道上的一樣的元素的不一樣的操做運行在同一個線程中。

不少有反作用的行爲參數能夠被轉換成無反作用的實現。通常來講println()這樣的反作用代碼不會有害。

ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
      .forEach(s -> results.add(s));  // 反作用代碼

上面的代碼能夠改爲無反作用的。

List<String>results =
    stream.filter(s -> pattern.matcher(s).matches())
          .collect(Collectors.toList());  // No side-effects!

排序 Ordering

某些流的返回的元素是有肯定順序的,咱們稱之爲 encounter order。這個順序是流提供它的元素的順序,好比數組的encounter order是它的元素的排序順序,List是它的迭代順序(iteration order),對於HashSet,它自己就沒有encounter order。

一個流是不是encounter order主要依賴數據源和它的中間操做,好比數據源List和Array上建立的流是有序的(ordered),可是在HashSet建立的流不是有序的。

sorted()方法能夠將流轉換成有序的,unordered()能夠將流轉換成無序的。 除此以外,一個操做可能會影響流的有序,好比map方法,它會用不一樣的值甚至類型替換流中的元素,因此輸入元素的有序性已經變得沒有意義了,可是對於filter方法來講,它只是丟棄掉一些值而已,輸入元素的有序性仍是保障的。

對於串行流,流有序與否不會影響其性能,只是會影響肯定性(determinism),無序流在屢次執行的時候結果多是不同的。

對於並行流,去掉有序這個約束可能會提供性能,好比distinct、groupingBy這些聚合操做。

結合性 Associativity

一個操做或者函數op知足結合性意味着它知足下面的條件:

(a op b) op c == a op (b op c) 對於併發流來講,若是操做知足結合性,咱們就能夠並行計算:

a op b op c op d == (a op b) op (c op d) 好比min、max以及字符串鏈接都是知足結合性的。

建立Stream

能夠經過多種方式建立流:

一、經過集合的stream()方法或者parallelStream(),好比Arrays.asList(1,2,3).stream()。 二、經過Arrays.stream(Object[])方法, 好比Arrays.stream(new int[]{1,2,3})。 三、使用流的靜態方法,好比Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n * 2),或者generate(Supplier<T> s)Stream.generate(Math::random)。 四、BufferedReader.lines()從文件中得到行的流。 五、Files類的操做路徑的方法,如list、find、walk等。 六、隨機數流Random.ints()。 七、其它一些類提供了建立流的方法,如BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()。 八、更底層的使用StreamSupport,它提供了將Spliterator轉換成流的方法。

中間操做 intermediate operations

中間操做會返回一個新的流,而且操做是延遲執行的(lazy),它不會修改原始的數據源,並且是由在終點操做開始的時候才真正開始執行。 Java的這種設計會減小中間對象的生成。

下面介紹流的這些中間操做:

distinct

distinct保證輸出的流中包含惟一的元素,它是經過Object.equals(Object)來檢查是否包含相同的元素。

List<String> l = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList());
System.out.println(l); //[a, b, c]

filter

filter返回的流中只包含知足斷言(predicate)的數據。

下面的代碼返回流中的偶數集合。

List<Integer> l = IntStream.range(1,10)
        .filter( i -> i % 2 == 0)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l); //[2, 4, 6, 8]

map

map方法將流中的元素映射成另外的值,新的值類型能夠和原來的元素的類型不一樣。

下面的代碼中將字符元素映射成它的哈希碼(ASCII值)。

List<Integer> l = Stream.of('a','b','c')
        .map( c -> c.hashCode())
        .collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]

flatmap

flatmap方法混合了map + flattern的功能,它將映射後的流的元素所有放入到一個新的流中。它的方法定義以下:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

能夠看到mapper函數會將每個元素轉換成一個流對象,而flatMap方法返回的流是將原流中元素的每個子元素組合成的流對象。

下面這個例子中將一首詩生成一個按行分割的流,而後在這個流上調用flatmap獲得單詞的小寫形式的集合,去掉重複的單詞而後打印出來。

String poetry = "Where, before me, are the ages that have gone?\n" +
        "And where, behind me, are the coming generations?\n" +
        "I think of heaven and earth, without limit, without end,\n" +
        "And I am all alone and my tears fall down.";
Stream<String> lines = Arrays.stream(poetry.split("\n"));
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));
List<String> l = words.map( w -> {
    if (w.endsWith(",") || w.endsWith(".") || w.endsWith("?"))
        return w.substring(0,w.length() -1).trim().toLowerCase();
    else
        return w.trim().toLowerCase();
}).distinct().sorted().collect(Collectors.toList());
System.out.println(l); 
//[ages, all, alone, am, and, are, before, behind, coming, down, earth, end, fall, generations, gone, have, heaven, i, limit, me, my, of, tears, that, the, think, where, without]

flatMapToDouble、flatMapToInt、flatMapToLong提供了轉換成特定流的方法。

limit

limit方法指定數量的元素的流。對於串行流,這個方法是有效的,這是由於它只需返回前n個元素便可,可是對於有序的並行流,它可能花費相對較長的時間,若是你不在乎有序,能夠將有序並行流轉換爲無序的,能夠提升性能。

List<Integer> l = IntStream.range(1,100).limit(5)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]

peek

peek方法方法會使用一個Consumer消費流中的元素,可是返回的流仍是包含原來的流中的元素。

可實現與forEach相同的功能,可是forEach是terminal 操做,執行後流就結束了,使用peek會返回原來的流。

String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
        .peek(System.out::println) //a,b,c,d
        .count();

sorted

sorted()將流中的元素按照天然排序方式進行排序,若是元素沒有實現Comparable,則終點操做執行時會拋出java.lang.ClassCastException異常sorted(Comparator<? super T> comparator)能夠指定排序的方式。

對於有序流,排序是穩定的。對於非有序流,不保證排序穩定。

String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l  = Arrays.stream(arr)
        .sorted((s1,s2) -> {
            if (s1.charAt(0) == s2.charAt(0))
                return s1.substring(2).compareTo(s2.substring(2));
            else
                return s1.charAt(0) - s2.charAt(0);
        })
        .collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]

skip

skip返回丟棄了前n個元素的流,若是流中的元素小於或者等於n,則返回空的流

終點操做 terminal operations

Match

public boolean 	allMatch(Predicate<? super T> predicate)
public boolean 	anyMatch(Predicate<? super T> predicate)
public boolean 	noneMatch(Predicate<? super T> predicate)

這一組方法用來檢查流中的元素是否知足斷言。

  • allMatch只有在全部的元素都知足斷言時才返回true,不然flase,流爲空時老是返回true
  • anyMatch只有在任意一個元素知足斷言時就返回true,不然flase,
  • noneMatch只有在全部的元素都不知足斷言時才返回true,不然flase,
System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true

count

count方法返回流中的元素的數量。它實現爲:

mapToLong(e -> 1L).sum();

collect

<R,A> R 	collect(Collector<? super T,A,R> collector)
<R> R 	collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

使用一個collector執行mutable reduction操做。輔助類Collectors提供了不少的collector,能夠知足咱們平常的需求,你也能夠建立新的collector實現特定的需求。它是一個值得關注的類,你須要熟悉這些特定的收集器,如聚合類averagingInt、最大最小值maxBy minBy、計數counting、分組groupingBy、字符串鏈接joining、分區partitioningBy、彙總summarizingInt、化簡reducing、轉換toXXX等。

第二個方法提供了更底層的功能,它的邏輯相似下面的僞代碼:

R result = supplier.get();
for (T element : this stream)
    accumulator.accept(result, element);
return result;

例子:

List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,
                                           ArrayList::addAll);
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
                                     StringBuilder::append)
                            .toString();

find

findAny()返回任意一個元素,若是流爲空,返回空的Optional,對於並行流來講,它只須要返回任意一個元素便可,因此性能可能要好於findFirst(),可是有可能屢次執行的時候返回的結果不同。 findFirst()返回第一個元素,若是流爲空,返回空的Optional。

forEach、forEachOrdered

forEach遍歷流的每個元素,執行指定的action。它是一個終點操做,和peek方法不一樣。這個方法不擔保按照流的encounter order順序執行,若是對於有序流按照它的encounter order順序執行,你可使用forEachOrdered方法。

Stream.of(1,2,3,4,5).forEach(System.out::println);

max、min

max返回流中的最大值, min返回流中的最小值。

reduce

reduce是經常使用的一個方法,事實上不少操做都是基於它實現的。 它有幾個重載方法:

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

第一個方法使用流中的第一個值做爲初始值,後面兩個方法則使用一個提供的初始值

Optional<Integer> total = Stream.of(1,2,3,4,5).reduce( (x, y) -> x +y);
Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);

值得注意的是accumulator應該知足結合性(associative)。

toArray()

將流中的元素放入到一個數組中。

組合

concat用來鏈接類型同樣的兩個流

public static <T> Stream<T> 	concat(Stream<? extends T> a, Stream<? extends T> b)

轉換

toArray方法將一個流轉換成數組,而若是想轉換成其它集合類型,須要調用collect方法,利用Collectors.toXXX方法進行轉換:

public static <T,C extends Collection<T>> Collector<T,?,C> 	toCollection(Supplier<C> collectionFactory)
public static  ... 	toConcurrentMap(...)
public static <T> Collector<T,?,List<T>> 	toList()
public static ...	toMap(...)
public static <T> Collector<T,?,Set<T>> 	toSet()

Stream開源工具

  1. protonpack

    • takeWhile and takeUntil(無限數據流上截取數據流)
    • skipWhile and skipUntil(無限數據流上截取數據流)
    • zip and zipWithIndex(將2數據流相同位置對應創建關係)
    • unfold
    • MapStream
    • aggregate
    • Streamable
    • unique collector
  2. java8-utils

相關文章
相關標籤/搜索