Java8 Stream新特性詳解及實戰

Java8 Stream新特性詳解及實戰java

背景介紹

在閱讀Spring Boot源代碼時,發現Java 8的新特性已經被普遍使用,若是再不學習Java8的新特性並靈活應用,你可能真的要out了。爲此,針對Java8的新特性,會更新一系列的文章,歡迎你們持續關注。數據庫

首先,咱們來看一下Spring Boot源代碼ConfigFileApplicationListener類中的一段代碼:編程

private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) {
    return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new)
            .filter((profile) -> !activatedViaProperty.contains(profile))
            .collect(Collectors.toList());
}

這段代碼怎麼?夠簡潔明快吧,若是不使用Java8的新特性,想象一下得多少行代碼才能實現?但若是沒掌握或不瞭解Java8的新特性,這段代碼讀起來是否是很酸爽?數組

Java 8的API中新增了一個處理集合的抽象概念:Stream,中文稱做「流」。它能夠指定你但願對集合進行的操做,能夠執行很是複雜的查找、過濾和映射數據等操做。使用起來就像使用SQL語句來對數據庫執行查詢操做同樣。可讓你如行雲流水通常寫出簡單、高效、幹勁的代碼。微信

什麼是Stream

Stream 中文稱爲 「流」,經過將集合轉換爲這麼一種叫作 「流」 的元素序列(注意是抽象概念),經過聲明性方式,可以對集合中的每一個元素進行一系列並行或串行的流水線操做。通俗來講就是你只用告訴「流」你須要什麼,便在出口處等待結果接口。dom

上圖爲Steam操做的基本流程,在後面的學習過程當中可反覆與具體的代碼進行對照,加深學習印象。ide

Stream相關概念

Stream操做的過程當中涉及到一些相關概念,先了解一下,方便後面統一稱謂。函數

  • 元素:特定類型的對象,好比List裏面放置的對象,會造成一個隊列。Stream不會存儲元素,只是按需計算。
  • 數據源:流的來源,對照上圖中的集合,數組,I/O channel, 產生器generator等。
  • 聚合操做:相似SQL語句的各類過濾操做,對照上圖中的filter、sorted、map等。
  • Pipelining:中文詞義「流水線」,中間操做會返回流自己,跟咱們以前所說的流式(fluent)編程一個概念,這樣可對操做進行優化,好比延遲執行(laziness)和短路(short-circuiting)。
  • 內部迭代:傳統遍歷方式是經過Iterator或For-Each來完成,這是外部迭代。而Stream經過訪問者模式(Visitor)實現了內部迭代。

須要注意的是在整個操做的過程當中,聚合操做部分能夠執行屢次操做,但每次操做並非像傳統的集合遍歷對集合裏面的元素進行轉換,而是將操做函數放入一個操做集合中,只有到最後一步(好比for-each打印)時纔會一次性執行。性能

而流和迭代器相似,只能迭代一次。好比,當調用完collect方法以後,流便不能再使用了。學習

stream操做方法分類

中間聚合操做:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered。

最終輸出操做:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator。

短路操做:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit。

生成流

在 Java 8 中, 生成流有多種方法:Stream接口的靜態工廠方法、集合提供的生成方法和其餘特殊的生成方法。

of方法

Stream接口的靜態工廠方法主要經過重載的of方法:

public static<T> Stream<T> of(T... values);

public static<T> Stream<T> of(T t)

of方法,其生成的Stream是有限長度的,Stream的長度爲其內的元素個數。使用示例代碼:

Stream<String> stringStream = Stream.of("公衆號");
Stream<String> stringsStream = Stream.of("關注","公衆號", "程序新視界");

generator方法

與of方法對應的generator方法生成的是無限長度的Stream,其元素是由Supplier接口提供的。

public static<T> Stream<T> generate(Supplier<T> s)

使用generate方法生成的Stream一般用於隨機數和常量,或者須要先後元素間維持着某種狀態信息的場景。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。

示例代碼以下:

Stream<Double> generateDouble = Stream.generate(Math::random);

Stream<String> generateString = Stream.generate(new Supplier<String>() {
    @Override
    public String get() {
        return "公衆號:程序新視界";
    }
});

其實上面兩種寫法的效果是同樣的,只不過第一種採用了Lambda表達式,簡化了代碼。

iterate方法

iterate方法生成的也是無限長度的Stream,是經過函數f迭代對給指定的元素種子而產生無限連續有序Stream,其中包含的元素能夠認爲是:seed,f(seed),f(f(seed))無限循環。示例代碼以下:

Stream.iterate(1,i -> i +1).limit(10).forEach(System.out::println);

打印結果爲1,2,3,4,5,6,7,8,9,10。

上面的方法能夠認爲種子(seed)爲1,f(seed)爲在1的基礎上「+1」,依次循環下去,直到達到limit的限制,最後生成對應的Stream。

empty方法

empty方法生成一個空的Stream,不包含任何元素。

Collection接口和數組的默認方法

Collection接口和數組中都提供了默認的生成Stream的方法。直接看源代碼:

// Collection中
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
// 並行流操做
default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

// Arrays中
public static <T> Stream<T> stream(T[] array) {
    return stream(array, 0, array.length);
}

public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
    return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}

示例代碼:

List<String> list = new ArrayList<>();
list.add("歡迎關注");
list.add("微信公衆號");
list.add("程序新視界");
list.stream().forEach(System.out::println);

int[] nums = new int[]{1, 2, 3, 4, 5};
Arrays.stream(nums).forEach(System.out::println);

其餘生成方法

關於其餘生成方法就不詳細舉例了,好比:Random.ints()、BitSet.stream()、JarFile.stream()、Pattern.splitAsStream(java.lang.CharSequence)、Files.lines(java.nio.file
.Path)等。

操做方法使用

關於操做方法就不進行詳細的講解,更多的以示例的形式展現如何使用。

concat方法

合併兩個Stream。若是輸入Stream有序,則新Stream有序;若是其中一個Stream爲並行,則新Stream爲並行;若是關閉新Stream,原Stream都將執行關閉。

Stream.concat(Stream.of("歡迎","關注"),Stream.of("程序新視界")).forEach(System.out::println);

distinct

Stream中元素去重。

Stream.of(1,1,1).distinct().forEach(System.out::println);

打印結果爲1。

filter

根據指定條件進行篩選過濾,留下知足條件的元素。

Stream.of(1, 2, 3, 4, 5).filter(i -> i >= 3).forEach(System.out::println);

打印結果爲3,4,5。

map

將Stream中的元素進行映射轉換,好比將「a」轉爲「A」,期間生產了新的Stream。同時爲了提高效率,官方也提供了封裝好的方法:mapToDouble,mapToInt,mapToLong。

Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println);
Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);

打印結果爲A,B,C。

flatMap

將流中的每個元素映射爲一個流,再把每個流鏈接成爲一個流。期間原有的Stream的元素會被逐一替換。官方提供了三種原始類型的變種方法:flatMapToInt,flatMapToLong和flatMapToDouble。

Stream.of(1, 2, 3).flatMap(i -> Stream.of(i * 10)).forEach(System.out::println);

打印結果爲10,20,30。

peek

生成一個相同的Stream,並提供一個消費函數,當新Stream中的元素被消費(執行操做)時,該消費函數會在此以前先執行。

Stream.of(1, 2).peek(i -> System.out.println("peekCall:" + i)).forEach(System.out::println);

打印結果依次爲:peekCall:1,1,peekCall:2,2。

ship

跳過前N個元素,取剩餘元素,若是沒有則爲空Stream。

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

打印結果爲3。

sorted

對Stream元素進行排序,可採用默認的sorted()方法進行排序,也可經過sorted(Comparator)方法自定義比較器來進行排序,前者默認調用equals方法來進行比較,

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

打印結果:1,2,3。

limit

限制返回前N個元素,與SQL中的limit類似。

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

打印結果爲:1,2。

collect

收集方法,實現了不少歸約操做,好比將流轉換成集合和聚合元素等。

Stream.of(1, 2, 3).collect(Collectors.toList());
Stream.of(1, 2, 3).collect(Collectors.toSet());

除了以上的集合轉換,還有相似joining字符串拼接的方法,具體可查看Collectors中的實現。

count

返回Stream中元素個數。

Stream.of(1, 2, 3).count();

forEach

遍歷Stream中全部元素。示例參考以上設計到的。

forEachOrder

遍歷Stream中全部元素,若是Stream設置了順序,則按照順序執行(Stream是無序的),默認爲元素的插入順序。

max

根據指定的比較器(Comparator),返回Stream中最大元素的Optional對象,Optional中的value即是最大值。

Optional能夠表明一個值或不存在,主要是爲了規避返回值爲null,而拋出NullPointerException的問題,也是由Java8引入的。但當調用其get()方法時,若是當前值不存在則會拋出異常。

Optional<Integer> max = Stream.of(1, 2, 3).max(Comparator.comparingInt(o -> o));
System.out.println("max:" + max.get());

打印結果:max:3。

min

與max操做相同,功能相反,取最小值。

Optional<Integer> min = Stream.of(1, 2, 3).min(Comparator.comparingInt(o -> o));
System.out.println("min:" + min.get());

打印結果:min:1。

reduce

reduce可實現根據指定的規則從Stream中生成一個值,好比以前提到的count,max和min方法是由於經常使用而被歸入標準庫中。實際上,這些方法都是reduce的操做。

Stream.of(1, 2, 3).reduce(Integer::sum);
Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);

以上兩個方法都是對結果進行求和,不一樣的是第一個方法調用的是reduce的reduce((T, T) -> T)方法,而第二個調用的是reduce(T, (T, T) -> T)。其中第二個方法的第一個參數0,表示從第0個值開始操做。

allMatch

判斷Stream中的全部元素是否知足指定條件。所有知足返回true,不然返回false。

boolean result = Stream.of(1, 2, 3).allMatch(i  -> i > 0);
System.out.println(result);

返回結果:true。

anyMatch

判斷Stream中的元素至少有一個知足指定條件。若是至少有一個知足則返回true,不然返回false。

boolean anyResult = Stream.of(1, 2, 3).anyMatch(i  -> i > 2);
System.out.println(anyResult);

返回結果:true。

findAny

得到其中一個元素(使用stream()時找到的是第一個元素;使用parallelStream()並行時找到的是其中一個元素)。若是Stream爲空,則返回一個爲空的Optional。

Optional<String> any = Stream.of("A", "B", "C").findAny();
System.out.println(any.get());

返回結果:A。

findFirst

得到第一個元素。若是Stream爲空,則返回一個爲空的Optional。

Optional<String> first = Stream.of("A", "B", "C").findFirst();
System.out.println(first.get());

返回結果:A。

noneMatch

判斷Stream中是否全部元素都不知足指定條件。都不知足則返回true,不然false。

boolean noneMatch = Stream.of(1, 2, 3).noneMatch(i  -> i > 5);
System.out.println(noneMatch);

返回結果:true。

統計

經過summaryStatistics方法可得到Stream的一些統計信息。

IntSummaryStatistics summaryStatistics = Stream.of(1, 2, 3).mapToInt((i) -> i).summaryStatistics();
System.out.println("max:" + summaryStatistics.getMax());
System.out.println("min:" + summaryStatistics.getMin());
System.out.println("sum:" + summaryStatistics.getSum());
System.out.println("average:" + summaryStatistics.getAverage());

這裏用到了流與數值流直接的轉換mapToInt,相似的方法還有mapToDouble、mapToLong。對應得到的數值流還提供了一些額外的方法,就像上面獲取不一樣統計信息的方法同樣。

鳥瞰Stream

最後,咱們再來看一下Stream的主要接口類關係圖。

其中,BaseStream定義了Stream的基本接口,Stream中定義了map、filter、flatMap等經常使用操做。IntStream、LongStream、DoubleStream是針對基本類型提供了便捷和特化操做。以上接口構建了Java8中流體系的根基。AbstractPipeline是流水線(Pipeline)的核心抽象類,用於構建和管理流水線。

另外,關於Stream的效率問題網上也有不少資料提到,下一篇文章,咱們將重點說說Stream的性能問題,關注公衆號「程序新視界」,敬請期待。

原文連接:《Java8 Stream新特性詳解及實戰

相關文章
相關標籤/搜索