JAVA8-Stream API

JAVA8-Stream API

只能以事先規定好的順序被讀取一次的數據的一個序列,稱之爲數據流。 —— Henzinger

Stream 特性

在JAVA8中,java.util.stream 中定義的一系列流API,是對 集合(Collection) 對象功能的增強,它專注於對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數據操作 (bulk data operation)。java8中的 Stream 更多的是對集合的操作,和 java.io 包裏的 InputStream 與 OutputStream 中的流是不一樣的。

特性如下:

  • 不保存數據
  • 不修改數據源但會產生結果(或副作用)
  • 流沒有大小限制
  • 流只能被訪問一次,當需要再次訪問時需要重新生成流

Stream 使用

當我們使用一個流的時候,通常包括三個基本步驟:

  • 獲取一個數據源(source)
  • 數據轉換
  • 執行操作獲取想要的結果

其中,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道。每次通過 Stream API 對集合和數組進行操作時,都需要先生成一個 Stream source。

Stream 創建

不同的對象有不同生成Stream的方式。主要如下:

  • 從 Collection 和數組
    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream(T array) or Stream.of()
  • 從 BufferedReader
    • java.io.BufferedReader.lines()
  • 靜態工廠
    • java.util.stream.IntStream.range()
    • java.nio.file.Files.walk()
  • 自己構建
    • java.util.Spliterator
  • 其他
    • Random.ints()
    • BitSet.stream()
    • Pattern.splitAsStream(java.lang.CharSequence)
    • JarFile.stream()

示例:

// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

由於基本數據類型的開箱和封箱過程特別耗時,所以提供了三種對應的包裝類型Stream:

  • IntStream
  • LongStream
  • DoubleStream

數值流的構造:

IntStream.of(new int[]{1, 2, 3});
IntStream.range(1, 3);
IntStream.rangeClosed(1, 3);

流的操作

將流創建成功之後,就需要對其進行操作,流的操作方式有兩種:中間操作與末端操作

  • Intermediate :一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。
    • 主要有: map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等操作
  • Terminal :一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,纔會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect(副作用)。
    • 主要有: forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等方式

還有一種操作被稱爲 short-circuiting。用以指:

  • 對於一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。
  • 對於一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。
  • 主要有 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit 等方式

當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要非充分條件。

示例:

  • map/flatMap

map的作用就是映射,將一個流映射成另一個流

String[] wordList = {"a","b","c"};
List<String> output = Arrays.stream(wordList).
    map(String::toUpperCase).
    collect(Collectors.toList());
//Arrays.stream(wordList) 構造一個流;
//map(String::toUpperCase) 中間操作,方法引用傳入一個行爲將流轉換爲另一個流
//collect(Collectors.toList()) 末端操作,將流轉換爲一個集合
System.out.println(output);

一對多的映射

Stream<List<Integer>> inputStream = Stream.of(
                Arrays.asList(1),
                Arrays.asList(2, 3),
                Arrays.asList(4, 5, 6)
        );
Stream<Integer> outputStream = inputStream.
                flatMap(Collection::stream);
System.out.println(outputStream.collect(Collectors.toList()));

輸出結果:[1, 2, 3, 4, 5, 6]

  • filter

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

Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
Stream.of(evens).forEach(System.out::print);

輸出結果:246

  • forEach

forEach 方法接收一個 Lambda 表達式,然後在 Stream 的每一個元素上執行該表達式。例如上面的 Integer[] sixNums = {1, 2, 3, 4, 5, 6};

  • findFirst

這是一個 termimal 兼 short-circuiting 操作,它總是返回 Stream 的第一個元素,或者空。

這裏比較重點的是它的返回值類型:Optional。作爲一個容器,它可能含有某值,或者不包含。使用它的目的是儘可能避免 NullPointerException。

Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。還有例如 IntStream.average() 返回 OptionalDouble 等等。

  • reduce

reduce主要作用是把 Stream 元素組合起來。類似於不動點定理(增量),當給定一個不動點,再按照某種規則將前面 Stream 的元素逐一組合。也有沒有不動點的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。

示例:

// 字符串連接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾,字符串連接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).
                reduce("", String::concat);
  • limit/skip

limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素

List<String> persons = new ArrayList();
for (int i = 1; i <= 10000; i++) {
    persons.add("string" + i);
}
List<String>personList2=persons.stream().limit(10).skip(3).collect(Collectors.toList();
//limit(10)返回流的前10個元素,skip(3)則拋除前3個
System.out.println(personList2);

輸出結果:

[string4, string5, string6, string7, string8, string9, string10]

  • sorted

通過 sorted 對 Stream 的排序,它比數組的排序更強之處在於你可以首先對 Stream 進行各類 map、filter、limit、skip 甚至 distinct 來減少元素數量後,再排序,這能幫助程序明顯縮短執行時間。

  • Match

Stream 有三個 match 方法,從語義上說:

  1. allMatch:Stream 中全部元素符合傳入的 predicate,返回 true
  2. anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
  3. noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true

Stream的轉換

對流進行操作後,需要轉換成其他數據結構

// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

擴展

Stream 還提供了 Stream.generate 方法自己創建流,通過實現Supplier接口,可以自己控制流的生成。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。由於它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。

//生成10個隨機數
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//或者
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章