第01章-Java SE8的流庫

從迭代到流的操作

  • 流表面上看起來和集合很類似,都可以讓我們轉換和獲取數據,但是它們之間存在著顯著的差異
    • 流並不存儲其元素,這些元素可能存儲在底層的集合中,或者是按需生成的
    • 流的操作不會修改其數據源
    • 流的操作是儘可能性執行的,這意味著直至需要其結果時,操作才會執行
  • 工作流的典型流程
    • 創建一個流
    • 指定將初始流轉換為其他流的中間操作,可能包含多個步驟
    • 應用終止操作,從而產生結果。這個操作會強制執行之前的惰性操作,從此之後,這個流就再也不能用了

流的創建

  • Collection介面的stream()方法:可以將任何一個集合轉化成流
  • Stream.of()
    • 作用:可以將數組轉化成流
    • 參數:具有可變長參數,所以可以使用其構建具有任意數量引元的流
  • Arrays.stream(array, from, to):可以從數組中於 from 和 to (不包括) 的元素中創建一個流
  • Stream.empty():創建不包含任何元素的流
  • Stream.generate():會接受一個不包含任何引元的函數 (從技術上講,是一個 Supplier <T> 介面的對象)。無論何時,只要需要一個流類型的值,該函數就會被調用以產生一個這樣的值
  • Stream.iterate():接受一個 「種子」 值,以及一個函數 (從技術上講,是一個 UnaryOperation <T>),並且會反覆地將該函數應用到之前的結果上
// Collection介面
List<String> words = new ArrayList<String>();
Stream<String> streamWords = words.stream();

// Stream.of()
String[] words = {"123", "456", "789"};
Stream<String> streamWords = Stream.of(words);

// Array.stream()
Stream<String> streamWords = Array.stream(words, 0, 3);

// Stream.generate()
// 產生一個隨機數流
Stream<Double> randomDouble = Stream.generate(Math::random);

// Stream.iterate()
// 產生一個 0,1,2,... 無限序列
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger>ONE));

filter、map 和 flatMap 方法

  • filter()方法
    • 作用:其會轉換產生一個流,其中的元素與某種條件相匹配
    • 參數:filter()的引元是Predicate<T>,即從 T 到 boolean 的函數
  • map()方法:將傳入的函數應用到每個元素上從而產生新的流
  • flatMap()方法:假設我們有一個泛型 G,以及將某種類型T轉換為 G<U> 的函數 f 和將類型 U 轉換為 G<V> 的函數 g,我們可以通過使用 flatMap 來組合它們,即首先應用 f,然後應用 g
// 使用 filter() 獲取大於12個字母的單詞
Stream<String> bigWords = words.stream().filter(w -> w.length() > 12);

// 使用 map() 將單詞全部轉為小寫
Stream<String> lowerCaseWords = words.stream().map(String::toLowerCase);

// 使用 flatMap() 將一個字流中流攤平為一個流
// 獲得形如 [["y", "o", "u", "r"],["a", "r", "e"]] 這樣的流中流
Stream<Stream<String>> result = words.stream().map(w -> letters(w));
// flatMap() 可將其攤平,獲得形如 ["y", "o", "u", "r", "a", "r", "e"] 這樣的
Stream<String> flatResult = words.stream().flatMap(w -> letters(w));

抽取子流和連接流

  • limit(n):將原流的前$n$個元素組成一個新的流返回
  • skip(n):返回丟棄原流的前$n$個元素形成的新流
  • Stream.concat()
    • 作用:將兩個流拼接起來
    • 注意:若第一個流是無限的,則第二個流將永遠得不到處理

其他的轉換流

  • distinct():返回一個由原流中剔除重複元素後所有的元素組成的新流
  • sort()
    • 直接對 Comparable 元素的流進行排序操作
    • 接受一個 Comparator
  • peek():其元素與原流相同,但是每回獲取一個元素時都會調用作為其參數的函數
ArrayList<String> words;  // "ccc", "a", "bb"

// sort() 直接對 Comparable 元素的流進行排序操作
words.stream().sort();  // "a", "bb", "ccc"

// sort() 接受一個 Comparator 作為參數
words.stream().sorted(Comparator.comparing(String::length).reverse());  // "ccc", "bb", "a"

簡單約簡

  • 約簡是一種終結操作,它們會將流約簡為可以在程式中使用的非流值
  • 常見簡約操作
    • long count():返迴流中元素個數
    • boolean anyMatch(Predicate<? super T> predicate):在這個流中任意元素匹配給定斷言時返回 true
    • boolean allMatch(Predicate<? super T> predicate):在這個流中所有元素匹配給定斷言時返回 true
    • boolean noneMatch(Predicate<? super T> predicate):在這個流中沒有任何元素匹配給定斷言時返回 true
    • Optional<T> max(Comparator<? super T> comparator):產生這個流的最大元素,使用由給定比較器定義的排序規則,如果這個流為空,會產生一個空的 Optional 對象
    • Optional<T> min(Comparator<? super T> comparator):產生這個流的最小元素,使用由給定比較器定義的排序規則,如果這個流為空,會產生一個空的 Optional 對象
    • Optional<T> findFirst():產生這個流的第一個元素,如果這個流為空,會產生一個空的 Optional 對象
    • Optional<T> findAny():產生這個流的任意一個元素,如果這個流為空,會產生一個空的 Optional 對象

Optional 類型

  • 定義
    • Optional<T>對象是一種包裝器對象,要麼包裝了類型 T 的對象,要麼沒有包裝任何對象
    • 對於上述的第一種情況,我們成為這種值是存在的
  • 作用:Optional<T>類型被當作一種更安全的方式,用來替代類型 T 的引用,這種引用要麼引用某個對象,要麼為 null

如何使用 Optional 值

  • 有效使用 Optional 值的關鍵:它在值不存在的情況下會產生一個可替代物,而只有在值存在的情況下才會使用這個值
  • 常用使用方法
    • T orElse(T other):產生這個 Optional 的值,或者在該 Optional 為空時,產生 other
    • T orElseGet(Supplier<? extends T> other):產生這個 Optional 的值,或者在該為空時,產生調用 other 的結果
    • <X extends Throwable> orElseThrow(Supplier<? extends X> exceptionSupplier):產生這個 Optional 的值,或者在該 Optional 為空時,拋出調用 exceptionSupplier 的結果
    • void ifPresent(Consumer<? extneds T> consumer):如果該 Optional 不為空,那麼就將它的值傳遞給 consumer
    • <U> Optional<U> map(Function<? super T, ? extneds U> mapper):只要這個 Optional 不為空且結果不為 null,將產生該 Optional 的值傳遞給 mapper 後的結果,否則產生一個空 Optional
// ifPresent():如果 optionalValue 可選值存在,則將其加入到 res 中
optionalValue.ifPresent(v -> res.add(v));

// map()
// added 具有三種值之一:在 optionalValue 存在的情況下包裝在 Optional 中的 true 或 false,以及在 optionalValue 不存在的情況下的空 Optional
Optional<Boolean> added = optionalValue.map(res::add);

不適合使用 Optional 值的方式

  • get():會在 Optional 值存在的情況下獲得其中包裝的元素,或者在不存在的情況下拋出一個 NoSuchElementException 對象
  • isPresent():如果 Optional 不為空,則返回 true
  • 注意:使用上述這兩種方法並不比直接使用包裝在其中的元素簡便或者安全

創建 Optional 值

  • Optional.of(value):創建一個 Optional 值;如果 value 為 null,其會拋出一個 NullPointerException 對象
  • Optional.ofNullable(value):創建一個 Optional 值;如果 value 為 null,其會產生一個空 Optional
  • Optional.empty():產生一個空 Optional

使用 flatMap 來構建 Optional 的值

  • 使用場景:假設你有一個可以產生 Optional<T> 對象的方法 f,並且目標類型具有一個可以產生 Optional<T> 對象的方法 g。如果需要將兩種方法結合起來,即將一種的結果作為另一種的參數,則需要使用 flatMap 才能正常結合
  • 返回值:如果 s.f() 的值存在,那麼 g 就可以應用到它上面,否則就會返回一個空 Optional

收集結果

  • iterator():產生可用於訪問元素的舊式風格迭代器
  • forEach()
    • 作用:將某個函數應用於所有元素
    • 注意:當應用在並行流上時,forEach 方法會以任意順序遍歷各個元素
  • forEachOrdered():按照流中的順序遍歷各個元素,但是這個方法會喪失並行處理的部分甚至全部
  • toArray()
    • 因為無法在運行時創建泛型數組,所以其會返回個 Object[]
    • 如果想要讓數組具有正確的類型,可以將其傳遞到數組構造器中
  • collect():其接受一個 Collector 介面的實例,可以將流中的元素收集到另一個目標中。Collectors 類提供了大量用於生成公共收集器的工廠方法
    • 收集到列表中:stream.collect(Collectors.toList())
    • 收集到集合中:stream.collect(Collectors.toSet())
    • 收集到特定的數據結構中:stream.collect(Collectors.toCollection(CertainDataStructure::new))
    • 通過連接操作收集流中所有字元串:stream.collect(Collectors.joining(str)),其中 str 是分隔符,不是必要的參數
    • 流中含有除字元串以外的對象:stream.map(Object::toString).collect(Collectors.joining())
  • Collectors.summarizingInt\Long\Double:如果想要將流的結果約簡為總和、平均值、最大值或最小值,可以使用 summarizingInt\Long\Double 方法中的某一個,這些方法會接受一個將流對象映射為數據的函數同時,這些方法會產生類型為 Int\Long\DoubleSummaryStatistics 的結果,同時計算總和、數量、平均值、最小值和最大值
// summarizingInt
IntSummaryStatistics statistics = stream.collect(Collectors.summarizingInt(String::length));
statistics.getSum();
statistics.getCount();
statistics.getAverage();
statistics.getMax();
statistics.getMin();

收集到映射表中

  • 方法:Collectors.toMap()
  • 實用方法
// 兩個參數:用來產生映射表的鍵和值
// 如果多個元素具有相同的鍵,收集器會拋出一個 IllegalStateexception 對象
Map<Integer, String> idToName = stream.collect(Collectors.toMap(Person::getId, Person::getName));
Map<Integer, String> idToPerson = stream.collect(Collectors.toMap(Person::getId, Function.identity()));

// 第三個參數:該函數會針對給定的已有值和新值來解決衝突並確定鍵對應的值
Map<Integer, String> idToPerson = stream.collect(Collectors.toMap(Person::getId, Function.identity(), 
                                                                 (existValue, newValue) -> existValue));

// 第三個參數:將構造器作為參數傳入
Map<Integer, String> idToPerson = stream.collect(Collectors.toMap(Person::getId, Function.identity(), 
                                                                 (existValue, newValue) -> existValue),TreeMap::new);

群組和分區

  • Collectors.groupingBy():將具有相同的值群聚成組
  • Collectors.partitioningBy():當分類函數是返回 boolean 的函數時,使用這個函數更加高效,其會產生一個鍵為 true/false 的映射表
// 將具有相同 id 的 Person 對象分為一類
Map<Integer, List<Person>> idToManyNames = stream.collect(Collectors.groupingBy(Person::getId));

// 分類函數為 boolean 函數時
Map<Boolean, List<Person>> idEqualsCertainId = stream.collect(Collectors.partitioningBy(
															  person -> person.id == 1));

下游收集器

  • 作用:如果要以某些方式來處理由 groupBy 得到的列表,就需要提供一個 「下游收集器」
  • 使用方法:將方法作為 groupBy 的第二個參數
  • 常用下游收集器
    • Collectors.toSet():獲得集合而不是列表
    • Collectors.counting():產生收集到的元素的個數
    • Collectors.summing():接受一個函數作為引元,將該函數應用到下游元素中,併產生它們的和
    • Collectors.maxBy():接受一個比較器,併產生下游元素中的最大值
    • Collectors.minBy():接受一個比較器,併產生下游元素中的最小值
    • Collectors.mapping():產生將函數應用到下游結果上的收集器,並將函數值傳遞給另一個收集器
    • Collectors.summarizingInt\Long\Double():獲取 Int\Long\DoubleSummaryStatistics
// mapping()
// 獲取一個州內具有最長名字的城市的名字
Map<String, Optional<String>> longestCityInState = cities.collect(
	groupingBy(City::getState, 
		mapping(City::getName,
			maxBy(Comparator.comparing()))));

約簡操作

  • 方法:reduce()
  • 作用:從流中計算某個值的通用機制
  • 幾種使用方法
    • 一個參數
      • 接受一個二元函數,並從前兩個元素開始持續應用它
      • 這個操作必須是可結合的
    • 兩個參數:第一個參數為這個操作的幺元,第二個參數為上述的二元函數
    • 三個參數:第一個參數為這個操作的幺元;第二個參數為上述的二元函數;第三個參數為一個結合函數,用於結合併行計算時產生的多個結果
// 三個參數
// 統計流中單詞的長度
int totalLength = stream.reduce(0,
                               (total, word) -> total + word.length(),
                               (total1, total2) -> total1 + total2);

基本類型流

  • 三種基本類型流:IntStream、LongStream、DoubleStream
  • 創建基本類型流
    • Int\Long|DoubleStream.of()
    • Arrays.stream()
  • 生成步長為$1$的整數流
    • Int\LongStream.range(a, b):不包括 b
    • Int\LongStream.rangeClosed(a, b):包括 b
  • CharSequence 介面中的兩種方法
    • codePoints:生成由字元的 Unicode 碼構成的 IntStream
    • chars:生成由 UTF-16 編碼機制的碼元構成的 IntStream
  • 對象流轉換成基本類型流:mapToInt\Long\Double()
  • 裝箱成對象流:boxed()
  • 基本類型流與對象流在方法上的差異
    • toArray 方法會返回基本類型數組
    • 產生可選結果的方法會返回一個 OptionalInt、 OptionalLong 或 OptionaIDouble,這些類與 Optional 類似,但是具有 getAsInt、getAsLong 和 getAsDouble 方法,而不是 get 方法
    • 具有返回總和、平均值、最大值和最小值的 sum、average、max 和 min 方法,對象流沒有定義這些方法
    • summaryStatistics 方法會產生一個類型為 IntSummaryStatistics、LongSummaryStatistics 或 DoubleSummaryStatistics 的對象

並行流

  • 獲取並行流
    • parallelStream():從集合中直接獲取一個並行流
    • parallel():將一個流轉化為並行流
  • 放棄排序需求:當放棄排序需求時,有些操作可以更有效的並行化,通過再流上調用 unordered() 方法,就可以明確的表示對排序不感興趣
  • 注意
    • 不要修改在執行某項流操作後會將元素返回到流中的集合 (即使這種修改是執行緒安全的)
    • 數據應該在記憶體中,必須等到數據到達是非常低效的
    • 流應該可以被高效地分成若干個子部分,由數組或平衡二叉樹支撐的流都可以工作得很好,但是 Stream. iterate 返回的結果不行
    • 流操作的工作量應該具有較大的規模,如果總工作負載並不是很大,那麼搭建並行計算時所付出的代價就沒有什麼意義
    • 傳遞給並行流操作的函數不應該被堵塞,並行流使用 fork-join 池來操作流的各個部分,如果多個流操作被阻塞,那麼池可能就無法做任何事情了