Java8新特性之Stream

前言

在想很好了解 Stream 以前,頗有必要簡單的瞭解下函數式變成以及Lambda的概念,能夠閱讀另一篇html

Java8新特性之Lambdajava

你們回憶下平常學習工做中使用的最多的 Java API 是什麼?相信不少人的答案和我同樣都是集合。咱們選擇適合的集合數據結構存儲數據,而咱們之於集合最多的操做就是遍歷,實現查詢,統計,過濾,合併等業務。程序員

哪裏用Stream

集合迭代

外部迭代:經過 for循環,Iterator迭代器遍歷集合,手動的拿到集合中每一個元素進行相應處理算法

  • 優勢
    • 對於程序的掌控更高
    • 性能強(若是算法功力深厚)
  • 缺點
    • 不少重複的模板代碼
    • 須要不少中間臨時變量來減小遍歷次數
    • 性能徹底取決於程序員水平,燒腦
    • 代碼不易讀
    • 容易出錯:例如for循環遍歷LinkedList會出錯

內部迭代:只提供對集合中元素的處理邏輯,遍歷過程交給庫類,Java5提供了foreach,Java8提供了Streamapi

  • 優勢
    • 代碼好讀
    • 簡單,只須要提供處理邏輯
  • 缺點
    • 有些狀況性能比外部迭代差一點點
    • 在使用foreach時不能對元素進行賦值操做

爲何要Stream

本文要介紹的Stream屬於內部迭代,以前咱們已經有了foreach減小了咱們的代碼量,爲何咱們還須要Stream呢?數組

  • 流水線的方式處理集合,結合Lambda爽歪歪
  • 代碼超短超好讀
  • Stream的開始到結束就至關於一次遍歷,容許咱們在遍歷中拼接多個操做
  • 強調的是對集合的計算邏輯,邏輯能夠屢次複用
  • 特別繁重的任務能夠很輕鬆的轉爲並行流,適合相似於大數據處理等業務
  • 成本很是小的實現並行執行效果

怎麼用Stream

上文咱們大概知道了Stream主要服務與集合,流的過程能夠總結爲三步:數據結構

  • 開始操做:讀取數據源(如集合)
  • 中間操做:組裝中間操做鏈,造成一條流的流水線
  • 終端操做:一次迭代執行流水線,結束流,並生成結果

中間操做和終端操做

第一步流加載集合數據,庫類完成咱們不須要關心,要想使用好Stream有必要從不一樣維度瞭解主要的操做併發

  • 中間操做:流水線的部件,返回的是this,也就是Stream,此時迭代並無執行
  • 終端操做:流水線真正開始執行,返回的是處理結果,終端操做事後流關閉
  • 無狀態操做:例如對集合中全部元素作轉換,或者過濾,不用保存別的元素的處理結果
  • 有狀態操做:例如排序,去重操做,須要保存以前集合中元素的狀態
  • 非短路操做:循序漸進完成迭代,返回處理結果
  • 短路操做:只有一達到預設的條件,馬上中止並返回

下圖給出了Stream給出的一些經常使用api的基本信息dom

基本數據類型流

爲了不自動裝箱拆箱消耗性能,Stream爲咱們提供了IntStream、DoubleStream和LongStream,分別將流中的元素特化爲int、long和double,這些特殊的流中提供了range,sum,max等數字類型經常使用的apiide

並行流

當咱們面對計算是否密集的應用開發時,爲了充分利用硬件資源,能夠簡單的經過改變parallel方法將流變成並行執行,可是在使用時有以下注意事項。

  • 並行流是經過 fork/join 線程池來實現的,該池是全部並行流共享的。默認狀況,fork/join 池會爲每一個處理器分配一個線程。假設你有一臺16核的機器,這樣你就只能建立16個線程。而與此同時其餘任務將沒法得到線程被阻塞住,因此使用並行流要結合機器和業務場景。
  • 避免在並行流中改變共享狀態,當心使用有狀態的操做
  • 並行流並不必定就快,要將多個線程的執行結果彙總

實戰

相信你們最關心的仍是實際開發中能幫助咱們解決哪些問題,經過一些簡單案例熟悉各類操做的用法

開始操做

生成空流

Stream<Object> empty = Stream.empty();

值生成流

Stream<String> stringStream = Stream.of("1", "2", "3");

數組生成流

String[] strings = { "1", "2", "3"};
Stream<String> stream = Arrays.stream(strings);

集合生成流

List<String> strings1 = Arrays.asList("1", "2", "3");
Stream<String> stream1 = strings1.stream();

文件生成流

Stream<String> lines = Files.lines(Paths.get("/c/mnt/"));

函數生成流(無限流)

// 無限流,流從0開始,下面的每一個元素依次加2
Stream<Integer> iterate = Stream.iterate(0, num -> num + 2);
// 無限流,流中每一個元素都是 0~1 隨機數
Stream<Double> generate = Stream.generate(Math::random);

數值範圍生成流

// 生成0到10的int流
IntStream intStream = IntStream.rangeClosed(0, 10);
// 生成0到9的int流
IntStream intStream1 = IntStream.range(0, 10);

手動生成流

// 生成有字符串a和數字1的異構的流
Stream.builder().add("a").add(1).build().forEach(System.out::print);

合併兩個流

Stream.concat(
    Stream.of("1", 22, "333"),
    Stream.of("1", 22, 333)
).forEach(System.out::print);

中間操做

在介紹中間操做時,爲了方便學習演示使用到了終端操做中的foreach方法,做用就和咱們寫的foreach循環相似,遍歷執行,不返回值。

過濾filter

// 過濾全部空字符串,注意是返回 true的留下
Stream.of("1", null, "2", "", "3")
    .filter(StringUtils::isNotEmpty)
    .forEach(System.out::print);

去重distinct

// 去掉重複的2
Stream.of("1", "2", "2", "2", "3")
    .distinct()
    .forEach(System.out::print);

跳過skip

// 跳過前兩個元素
Stream.of("1", "2", "3", "4", "5")
    .skip(2)
    .forEach(System.out::print);

截短limit

// 與skip相反,只留下前2個
Stream.of("1", "2", "3", "4", "5")
    .limit(2)
    .forEach(System.out::print);

映射map

@Data
@AllArgsConstructor
public class Person {
    private String name;
}

// 將流中每一個字符串轉爲Person實例
Stream.of("1", "2", "3")
    .map(Person::new)
    .forEach(System.out::print);

// 將流每一個字符串變成其長度,爲了不自動拆箱,使用mapToInt轉爲IntStream
Stream.of("1", "2", "3")
    .mapToInt(String::length)
    .forEach(System.out::print);

扁平映射flatMap

用法和map相似,當咱們須要把流中的每一個元素全映射另一個流,也就是數據在流中流裏面,這時候操做就不方便,藉助flatMap,咱們能夠把全部第二層流中的元素合併到最外層流

// 每一個字符串按照逗號分隔合併成一個流
Stream.of("1,2,3", "4,5,6", "7,8,9")
    .flatMap(a -> Stream.of(a.split(",")))
    .forEach(System.out::print);

排序sorted

Stream.of("4", "3", "5")
    .sorted()
    .forEach(System.out::print);

Stream.of("4", "3", "5")
    .sorted(Comparator.naturalOrder())
    .forEach(System.out::print);

並行流相關parallel,sequential,unordered

// 串行流轉並行流
Stream.of("1", "2", "3").parallel();
// 並行流轉串行流
Arrays.asList("1", "2", "3").parallelStream().sequential();

// 在並行流中加上unordered,使得流變成無序,提供並行效率,此時limit至關於隨機取2個元素
Arrays.asList("1", "2", "3", "4", "5")
    .parallelStream()
    .unordered()
    .limit(2).forEach(System.out::print);

調試peek

用於debug調試代碼,在每次執行操做前,看一眼元素

// 每一個元素會打印兩遍
Stream.of("1", "2", "3").peek(System.out::print).forEach(System.out::print);

終端操做

遍歷foreach

Stream.of("1", "2", "3").forEach(System.out::print);

// 並行流中使用用於保持有序
Arrays.asList("1", "2", "3").parallelStream().forEachOrdered(System.out::print);

通用統計count,max,min

// 總個數,返回true|false
Stream.of("1", "2", "3").count();
// 最大元素,返回Optional
Stream.of("1", "2", "3").max(Comparator.naturalOrder());
// 最小元素,返回Optional
Stream.of("1", "2", "3").min(Comparator.naturalOrder());

數值流特有統計sum,average,summaryStatistics

以 IntStream 舉例

// 累加求和
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).sum();
// 求平均數
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).average();
// 總和,最大值,最小值,平均數,總個數,應有盡有
Stream.of("1", "2", "3").mapToInt(Integer::valueOf).summaryStatistics();

匹配match

返回true|false

// 是否有長度大於 2 的字符串
Stream.of("1", "22", "333").anyMatch(s -> s.length() > 2);
// 是否一個長度大於 2 的字符串也沒有
Stream.of("1", "22", "333").noneMatch(s -> s.length() > 2);
// 是否字符串長度全大於 2
Stream.of("1", "22", "333").allMatch(s -> s.length() > 2);

查找find

因爲是短路操做,因此只有在串行流中findAny和findFirst才區別明顯

// 找到流中任意一個元素,普通流通常也返回第一個元素,並行流中返回任意元素
Stream.of("1", "22", "333").findAny();
// 找到流中第一個元素,普通流和並行流都同樣
Stream.of("1", "22", "333").findFirst();

彙總collect

把全部處理結果彙總,Collectors收集器裏提供了不少經常使用的彙總操做

// 將結果彙總成一個list
Stream.of("1", "22", "333").collect(Collectors.toList());

歸約reduce

谷歌著名的map-reduce理想,用於最後彙總結果

// 0做爲起始的pre,流的結果等於pre乘以自身再加一,直到curr到達最後一個元素
// (1) pre = 0 curr = 1 計算 pre = 0 * 1 + 1 = 1 
// (2) pre = 1 curr = 2 計算 pre = 1 * 2 + 1 = 3
// (3) pre = 3 curr = 3 計算 pre = 3 * 3 + 1 = 10
// (4) pre = 10 curr = null 返回結果 10
IntStream.of(1, 2, 3).reduce(0, (pre, curr) -> pre * curr + 1);

// 固然若是不設置初始值,流中第一個元素就是pre
IntStream.of(1, 2, 3).reduce((pre, curr) -> pre * curr + 1);

轉數組toArray

Stream.of("1", "22", "333").toArray();

得到迭代器iterator

Stream.of("1", "2", "3").iterator();

得到並行可分迭代器spliterator

Stream.of("1", "2", "3").spliterator();

流類型判斷isParallel

Stream.of("1", "2", "3").isParallel();

收集器進階

在介紹 collect 操做中,咱們用了 Collectors 中提供的 toList 方法將結果彙總成List,collect 是很經常使用的操做Collectors中有不少有用的方法值得熟悉一下,其實不少終端方法都是 collect 的快捷寫法,若是都不能知足需求咱們還能夠本身實現一個

轉經常使用集合

// 轉list
List<String> collect = Stream.of("1", "2", "3").collect(Collectors.toList());

// 轉set
Set<String> collect = Stream.of("1", "2", "3").collect(Collectors.toSet());

// 轉map,key爲字符串長度,value爲字符串自己
Map<Integer, String> collect = Stream.of("1", "2", "3")
    .collect(Collectors.toMap(String::length, Function.identity()));

// 轉併發版map,key爲字符串長度,value爲字符串自己
Map<Integer, String> collect = Stream.of("1", "2", "3")
    .collect(Collectors.toConcurrentMap(String::length, Function.identity()));

// 轉指定類型集合
ArrayList<String> collect = Stream.of("1", "2", "3")
    .collect(Collectors.toCollection(ArrayList::new));

拼接字符串

// 拼接成一個字符串
String collect = Stream.of("1", "2", "3").collect(Collectors.joining());

// 拼接成一個字符串,逗號分隔
String collect = Stream.of("1", "2", "3").collect(Collectors.joining(","));

統計

都有對應簡化版,一些更加靈活多變的操做能夠用Collectors

// 和終端操做中的 max 等價
Stream.of("1", "2", "3").collect(Collectors.maxBy(Comparator.naturalOrder()));

// 和終端操做中的 min 等價
Stream.of("1", "2", "3").collect(Collectors.minBy(Comparator.naturalOrder()));

// 和終端操做中的 count 等價
Stream.of("1", "2", "3").collect(Collectors.counting(Integer::valueOf));

// 和數值流終端操做中的 sum 等價
Stream.of("1", "2", "3").collect(Collectors.summingInt(Integer::valueOf));

// 和數值流終端操做中的 average 等價
Stream.of("1", "2", "3").collect(Collectors.averagingInt(Integer::valueOf));

// 和數值流終端操做中的 summaryStatistics 等價
Stream.of("1", "2", "3").collect(Collectors.summarizingInt(Integer::valueOf));

分組

能夠和其餘收集器方法任意組合

// 以字符串長度分紅3組,map的key爲長度,value爲對應長度字符串list
Map<Integer, List<String>> collect = Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.groupingBy(String::length));

// 以字符串長度分紅3組,map的key爲長度,value爲對應的元素個數
Map<Integer, Long> collect = Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.groupingBy(String::length, Collectors.counting()));

// 這個例子沒有實際意義,展現咱們能夠進行二級分組,長度分組完畢,再組內以hash值分組
Map<Integer, Map<Integer, List<String>>> collect = Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.groupingBy(
        String::length,
        Collectors.groupingBy(String::hashCode)
    ));

分區

分區時特殊的分組,使用方法相似,特殊是經過謂詞表達式只能分紅兩組,true是一組,false是一組

// 以長度大於2爲標準分區
Map<Boolean, List<String>> collect = Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.partitioningBy(s -> s.length() > 2));

歸約

// 和終端操做 reduce 等價
Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.reducing(0, Integer::valueOf, Integer::sum))

多操做鏈接

// 把流彙總成list,而後再求出其容量
Integer collect = Stream.of("1", "22", "33", "4", "555")
    .collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

自定義

有些時候默認的實現有缺陷,或者追求更高的性能咱們須要本身實現收集器。只要實現 Collector<T, A , R>接口 中的方法咱們就能夠得到本身的收集器,其中 T 是元素泛型,A是累加器結果,R是最終返回結果,全部首先咱們來看下要實現哪些方法

  • supplier:提供一個容器 A 裝結果
  • accumulator:累加器,將元素累加進剛纔建立的容器
  • combiner:合併容器的結果
  • finisher:完成操做,將 A 轉爲 R 返回
  • characteristics:是個定義標識的方法
    • UNORDERED:結果不受流中項目的遍歷和累積順序的影響
    • CONCURRENT:accumulator函數能夠從多個線程同時調用
    • IDENTITY_FINISH:表示 finisher 沒作任何事情,直接返回了累加的結果,也就是A和R相同
public static final Collector<String, List<String>, List<String>> myToList = Collector.of(
    // supplier: 建立 A(ArrayList)
    ArrayList::new,
    // accumulator:把每一個元素放入 A 中
    (list, el) -> list.add(el),
    // combiner:若是並行拆分紅多個流,直接 addAll 合併
    // 若是不想支持並行能夠寫個空,或拋UnsupportedOperationException異常
    (listA, listB) -> {
        listA.addAll(listB);
        return listA;
    },
    // finisher:不作任何事情,直接返回 A
    Function.identity(),
    // characteristics...:表示 A R 類型相同, 且支持並行流
    Collector.Characteristics.IDENTITY_FINISH,
    Collector.Characteristics.CONCURRENT
);

// 自定義收集器轉成list
List<String> collect = Stream.of("1", "22", "33", "4", "555").collect(myToList);
相關文章
相關標籤/搜索