「Java 8 函數式編程」讀書筆記——高級集合類和收集器

本章是該書的第五章, 主要講了方法引用和收集器java

方法引用

形如:git

artist -> artist.getName()
(String arg) -> arg.length()

這樣的表達式, 能夠簡寫爲:github

Artist::getName
String::length

這種簡寫的語法被稱爲方法引用. 方法引用無需考慮參數, 由於一個方法引用能夠在不一樣的狀況下解析爲不一樣的Lambda表達式, 這依賴於JVM的推斷.segmentfault

方法引用的類型

方法引用能夠分爲四類:安全

  • 引用靜態方法: ClassName::staticMethodName, 好比: String.valueOf數據結構

  • 引用特定實例方法: object::instanceMethodName, 好比: str::toString併發

  • 引用特定類型的任意對象的實例方法: ClassName::instanceMethodName, 好比: String::lengthapp

  • 引用構造方法: ClassName::new, 好比: String::new框架

元素順序

當咱們對集合進行操做時, 有時但願是按照必定的順序來操做, 而有時又但願是亂序的操做. 有兩個方法能夠幫助咱們進行順序的操做.ide

亂序

BaseStream.unordered()方法能夠打亂順序, 科技將原本有序的集合變成無序的集合

排序

Stream.sorted方法有兩個簽名, 一個無參, 一個有參數Comparator<? super T> comparator

  • 無參的方法要求T實現了Comparable接口

  • 有參方法須要提供一個比較器

收集器

收集器是一種通用的, 從流中生成複雜值的結構. 將其傳給collect方法, 全部的流就均可以使用它. 而下面提到的單個收集器, 均可以使用reduce方法模擬.

轉換成集合

咱們可使用Collectors中的靜態方法toList() toSet()等, 將流收集爲ListSet

stream.collect(toList())
stream.collect(toSet())

咱們不須要關心具體使用的是哪種具體的實現, Stream類庫會爲咱們選擇. 由於咱們能夠利用Stream進行並行數據處理, 因此選擇是否線程安全的集合十分重要.

固然咱們也能夠指定使用哪種實現來進行收集:

stream.collect(toCollection(ArrayList::new))

轉換成值

Collectors類提供了不少的方法用於轉化值, 好比counting maxBy minBy等等, 能夠查看javadoc瞭解.

目前瞭解到的是, 這三個方法均可以使用Stream中的count max min方法代替, 而不須要做爲collect方法的參數

數據分割

有時咱們想按照一個條件把數據分紅兩個部分, 而不是隻獲取符合條件的部分, 這時可使用partitioningBy方法收集. 將它傳入collect方法, 能夠獲得一個Map<Boolean, List>, 而後就能夠對相應的數據進行處理了.

數據分組

groupingBy方法能夠將流分紅多個List, 而不單單是兩個, 接收一個Lambda表達式做爲參數, 其返回值做爲key, 最後的結果也是一個Map, 形如Map<String, List>. 這一方法相似於SQL中的group by

生成字符串

若是要從流中獲得字符串, 能夠在獲得Stream<String>以後使用Collectors.joining方法收集. 該方法接收3個String參數, 分別是分隔符 前綴 後綴

artists.stream()
  .map(Artist::getName)
  .collect(Collectors.joining(",", "[", "]"));

組合收集器

咱們能夠將收集器組合起來, 達到更強的功能. 書上舉了兩個栗子

  • 例一

public Map<Artist, Long> numberOfAlbums(Stream<Album> albums) {
  return albums
    .collect(
    groupingBy(Album::getMainMusicina, counting()));
}

這個方法的目的是統計每一個歌手的做品數目. 若是不組合收集器, 咱們先用groupingBy獲得一個Map<Artist, List<Album>>以後, 還要去遍歷Map獲得統計數目, 增長了代碼量和性能開銷.

上面的counting方法相似於count方法, 做用於List<Album>的流上.

  • 例二

public Map<Artist, List<String>> nameOfAlbums(Stream<Album> albums) {
  return albums
    .collect(
    groupingBy(Album::getMainMusician,
              mapping(Album::getName, toList())));
}

這個方法的目的是獲得每一個歌手的做品名稱列表. 若是不組合收集器, 咱們將會先獲得一個Map<Artist, List<Album>>. 然而, 咱們只想獲得做品名稱, 也就是一個List<String>, 組合mapping收集器能夠幫助咱們實現效果.

mapping收集器的功能相似於map, 將一種類型的流轉換成另外一種類型. 因此相似的, mapping並不知道要把結果收集成什麼數據結構, 它的第二個參數就會接收一個普通的收集器, 好比這裏的toList, 來完成收集.

這裏的countingmapping是咱們用到的第二個收集器, 用於收集最終結果的一個子集, 這些收集器叫作下游收集器.

定製收集器

定製收集器看起來麻煩, 其實抓住要點就好了.

使用reduce方法

前面說過, 這些收集器均可以使用reduce方法實現, 咱們定製收集器, 實際上就是爲reduce方法編寫三個參數, 分別是:

  • identity

  • accumulator

  • combiner

關於這三個參數的意義, 若是不太理解, 能夠看看這個答案: https://segmentfault.com/q/1010000004944450

咱們能夠設計一個類, 爲這三個參數設計三個方法, 再提供一個方法用於獲取目標類型(若是這個類就是目標類型的話, 能夠不提供這個方法)

實現Collector接口

若是不想顯式的使用reduce方法, 咱們只須要提供一個類, 實現Collector接口.

該接口須要三個泛型參數, 依次是:

  • 待收集元素的類型

  • 累加器的類型

  • 最終結果的類型

須要實現的方法有:

  • supplier: 生成初始容器

  • accumulator: 累加計算方法

  • combiner: 在併發流中合併容器

  • finisher: 將容器轉換成最終值

  • characteristics: 獲取特徵集合

多數狀況下, 咱們的容器器和咱們的目標類型並不一致, 這時, 須要實現finisher方法將容器轉化爲目標類型, 好比調用容器的toString方法.

有時咱們的目標類型就是咱們的容器, finisher方法就不須要對容器作任何操做, 而是經過設置characteristicsIDENTITY_FINISH, 使用框架提供的優化獲得結果.

詳細講解能夠參見http://irusist.github.io/2016/01/04/Java-8%E4%B9%8BCollector/

Map新增方法

Java 8Map新增了不少方法, 能夠經過搜索引擎輕鬆找到相關文章. 這裏舉幾個書中提到的相關方法.

  • V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

  • V computeIfPresent(K key, BiFunction<? super K, ? super V, extends V> remappingFunction)

  • V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

這三個方法相似, 都是根據key來處理, 只是Lambda表達式的執行條件不一樣, 從函數名就能夠看出來. 不過要注意Lambda表達式的參數, 第一個方法的Lambda只須要一個參數key, 後面兩個方法的Lambda須要兩個參數keyvalue, 而compute方法的Lambda中的value參數可能爲null.

  • V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

此方法用於合併value, 新value在第二個參數給出. Lambda表達式規定合併方法, 其兩個參數依次是oldValuenewValue, oldValue是原Mapvalue, 可能爲空; newValuemerge方法的第二個參數.

  • void forEach(BiConsumer<? super K, ? super V> action)

經過forEach方法, 再也不須要使用外部迭代來遍歷Map.

相關文章
相關標籤/搜索