Java8 Stream API使用

Java8面世後,目前工做中使用最多的特性就當是Stream API。Stream API結合lamada表達式帶來的是全新的編程體驗,以往一些繁瑣的數據處理,現在不只條理清晰,並且代碼量至少減小了一半。Stream API使用一段時間後,我就不知足於零碎的一些經常使用組合方式,但願對Stream有一個輪廓性的認識。於是在參考了官方文檔和衆多網上資料後,本身對Stream API作了一些小總結,以期達到梳理和記錄之用。java

1.認識Stream

Java8對Stream的定義是這樣的數據庫

A sequence of elements supporting sequential and parallel aggregate operations.編程

簡單的翻譯就是支持順序和並行的匯聚操做的一組元素api

然而這個解釋看上去沒有什麼用,更通俗的說法是Stream是一個高級的Iterator,相對於原始的Iterator顯示地遍歷元素並執行操做,Stream在其內部隱式地進行數據轉換。數組

另外Stream也能夠理解成一個管道,它並不是數據結構,不保存數據,input的數據從管道一頭進入,管道中完成定義好的處理方式,從另外一頭輸出你要的結果。於是Stream是單向的,不可往復的,想要屢次使用只有建立新的Stream。數據結構

Stream的處理過程可分爲三個部分,咱們舉個例子,對1,2,3三個數的平方求和。框架

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream().mapToInt(n -> n * n).sum();

以上的操做包含了Stream的三部分,分別是建立Stream轉換Stream聚合Streamdom

下面就依次分析下這三個部分ide

2.建立Stream

經常使用的建立Stream的方式有三種:函數

  1. 經過集合類的相關方法
  2. 經過Stream接口的靜態工廠方法
  3. 其餘能產生流的類的方法

2.1 集合類的相關方法

Collection接口中定義了一個stream方法

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

於是全部Collection的子類都能經過此方法開啓Stream,常見的就是從數據庫查詢出一個List,而後使用Stream進行處理。

List<User> userList = // 數據庫查詢
long teenagerNum = userList.stream().filter(u -> u.getAge() < 20).count();

從數據庫查詢出一組User,得到出年齡小於20歲的用戶數。

還有一種狀況是,參數是一個數組,可使用Arrays工具類來處理

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

distinct方法用來去重,即將一組整數去重後打印出來

2.2 Stream靜態工廠方法

Stream接口中也提供多種靜態方法來輔助咱們構造Stream

1.of方法,很是好用的方法,接受可變參數

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

或者直接傳入數組

String[] strs = {"1", "2", "3"};
Stream<String> stringStream = Stream.of(strs);

2.generator方法,生成一個無限長度的Stream,須要一個自定義的Supplier類,好比建立一個隨機數的Stream

Stream.generate(new Supplier<Long>() {
@Override
    public Long get() {
        return Math.random();
    }
});

可使用lamada表達式及Method Reference簡化

Stream.generate(() -> Math.random());
Stream.generate(Math::random);

通常無限長度的Stream都會配合Stream的limit方法來使用

3.iterate方法,一樣生成無限長度的Stream,但它是對給定的種子(seed)反覆調用用戶指定函數來生成,其生成的元素可認爲是:seed,f(seed),f(f(seed))無限循環

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

4.concat方法,將兩個Stream組合成一個Stream

Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("4", "5");
Stream.concat(stream1, stream2);

5.builder方法,使用追加的方式創建Stream而後消費

Stream.builder()
    .add("1")
    .add("2")
    .build();

2.3 其餘方式

Java也在其餘可能有Stream操做的類中增長了便捷的方法

// 文件讀取
java.io.BufferedReader.lines()
// 文件遍歷
java.nio.file.Files.walk()
// 隨機數
java.util.Random.ints()
// 正則匹配
Pattern.splitAsStream(java.lang.CharSequence)

3.轉換Stream

轉換Stream就是把一個Stream經過某些行爲轉換成新的Stream。Stream接口中定義了經常使用的幾個轉換方法,但在實際使用中確實大大的方便。

3.1 distinct

對於Stream中包含的元素進行去重(依賴元素的equals方法)

以前對一個List進行去重,都是藉助HashSet來作

List list2 = new ArrayList(new HashSet(list));

如今也可使用distinct來作

List list2 = list.stream().distinct().collect(Collectors.toList());

3.2 filter

對Stream中包含的元素使用給定的過濾函數進行過濾,新生成的Stream只包含符合條件的元素

List<Integer> numbers = Arrays.asList(-1, 1, 0);
numbers.stream().filter(n -> n > 0).collect(Collectors.counting());

3.3 map

對Stream中包含的元素使用給定的轉換函數進行轉換,新生成的Stream只包含轉換生成的元素

Stream.of(1, 2, 3).map(n -> n + 1).collect(Collectors.toList());

這個方法由三個對於原始數據類型的變種方法,分別是mapToIntmapToLong, mapToDouble, 主要是減小自動裝箱和拆箱的性能消耗。

3.4 flatMap

和map類似,但通常用來將多層級扁平化,舉個例子你們就清楚了。

有個兩層結構的數據結構

[ [1, 2, 3], [4, 5], [6] ]

把它所有攤平

[1, 2, 3, 4, 5, 6]

就可使用flatMap

Stream<List<Integer>> intStream = Stream.of(Arrays.asList(1, 2, 3),Arrays.asList(4,5),Arrays.asList(6));
        intStream.flatMap(childList -> childList.stream()).forEach(System.out::println);

3.5 peek

生成一個包含原Stream全部元素的新Stream,同時提供一個消費函數(Consume),當Stream中每一個元素被消費時都會執行給定的消費函數。

舉個例子來講,對1到9求和,求和前打印全部求和元素。由於Stream是單向的,作了求和就不能再打印了,怎麼辦呢?用peek方法。

int sum = IntStream.range(1, 10).peek(System.out::println).sum();
System.out.println("sum:" + sum);

3.6 limit

對Stream進行截斷操做,獲取其前N個元素,若是原Stream中包含元素個數小於N,就獲取其全部元素。

IntStream.range(1, 10).limit(5).forEach(System.out::println);

3.7 skip

丟棄Stream前N個元素,返回剩下元素組成的新Stream,若是原Stream中包含的元素個數小於N,返回空Stream。

好比打印一個數組的第10到20個元素

IntStream.range(1, 100).skip(10).limit(10).forEach(System.out::println);

3.8 sorted

對Stream中的元素按默認方式排序或者指定比較器排序

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

4.聚合Stream

聚合操做接受一個元素序列做爲輸入,反覆使用某個合併操做,把序列中的元素合併成一個彙總的結果。好比求元素的總和或最大值,或者把元素累積成一個List對象。Stream接口有一些通用的匯聚操做,好比reduce和collect;也有特定用途的匯聚操做,好比sum,max,count。

4.1 collect

將Stream中的元素收集到一個結果容器中。java8還爲collect方法提供了工具類Collectors,能夠方便地生成List,Set,Map等集合。

// 獲取userId的List
List<Long> userIdList = users.stream().map(u -> u.getId()).collect(Collectors.toList());
// 獲取userId的Set
Set<Long> userIdSet = users.stream().map(u -> u.getId()).collect(Collectors.toSet());
// 獲取userId的Map
Map<Long, User> userIdMap = users.stream().collect(Collectors.toMap(u -> u.getId(), u -> u));

另外Collectors還提供了兩種很是經常使用的聚合方式:分組和分片,分別對應groupingBy和partitioningBy兩個方法。怎麼用呢,咱們來看例子。

我想將用戶按所在地址分組,北京的放在一塊兒,上海的放在一塊兒,能夠用groupingBy。

Map<String, List<User>> userAddressMap = users.stream().collect(Collectors.groupingBy(User::getAddress));

或者我想將用戶按年齡區分,20歲以上爲一個分片,20歲如下爲一個分片,能夠用partitioningBy。

Map<Boolean, List<User>> userAgeMap = users.stream().collect(Collectors.partitioningBy(u -> u.getAge() > 20));

這兩個的區別也就顯而易見了,groupingBy是按照某個內部字段進行分組,而partitioningBy是按照某個條件將元素分紅是和非兩個分片。

Collectors中還有其餘很便捷的方法,有興趣的能夠研究下。

4.2 reduce

上面說的collect能夠當作對Stream元素進行不一樣方式的彙集,而reduce則是對Stream元素進行指定方式的聚合。

好比我想求用戶的最大年齡,可使用reduce來操做。

Optional optional = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2);

這個方法返回值是Optional,它是java8防止出現空指針的一種方式。要得到最大的年齡,調用Optional的get方法便可。

Integer maxAge = users.stream().map(User::getAge).reduce((a1, a2) -> a1 > a2 ? a1 : a2).get();

可是這種方式仍是可能會出現空指針,咱們能夠在reduce時設置一個默認值。

Integer maxAge = users.stream().map(User::getAge).reduce(0 ,(a1, a2) -> a1 > a2 ? a1 : a2);

這樣就不須要Optional作一次轉換了。

像最大,最小,計數這些是常見的計算方式,在Stream中也都提供了直接的方法供咱們使用。

  • max(Comparator comp)
  • min(Comparator comp)
  • count()

對於max,min方法須要提供Compartor比較器。若是是在諸如mapToInt,mapToLong,mapToDouble之類的基礎數據操做後,則不用提供比較器,另外還支持求和(sum)和平均(average)方法。

users.stream().mapToInt(User::getAge).sum();
users.stream().mapToInt(User::getAge).average();

4.3 搜索相關

Stream API還提供了幾種快捷的搜索方法,支持在一組元素中的經常使用搜索。

  • allMatch:全部元素都知足匹配條件
  • anyMatch:任一元素知足匹配條件
  • findFirst:返回Stream中的第一個元素
  • findAny:返回Stream中的任意個元素
  • noneMatch:全部元素都不知足匹配條件

4.4 遍歷

將Stream中的元素逐個按照指定方式進行消費。

打印全部用戶

users.stream().forEach(System.out::println);

5.Stream API的優勢

  1. Stream轉換操做是惰性化的(lazy),即屢次轉換操做在聚合操做時只需一次循環就能完成,並不會由於屢次轉換形成額外的循環開銷。
  2. 由於1的緣由,Stream的數據源能夠是無限的,它並不會像普通iterator同樣須要把全部數據都加載到內存中。好比Stream的generate方法和limit方法相結合,能夠從無限的數據源中操做本身想要的數據。
  3. Stream能夠並行化操做,它依賴於java7中引入的Fork/Join框架來拆分任務和加速處理過程。對於Collection子類,直接使用parallelStream方法便可開啓並行化操做。不過並行化也是會有額外的開銷的,所以要適當地使用。

6.Stream API用例

以庫存實體Stock爲例

public class Stock {

    // 主鍵id
    Long id;
    // 商品id
    Long skuId;
    // 供應商id
    int supplierId;
    // 狀態(0:不可用,1:可用,2:任務中)
    int status;
    // 庫存量
    BigDecimal amount;
}

通常都是根據條件從數據庫查詢出一個Stock的List,變量名爲stockList,從這個List出發,介紹一些經常使用的Stream的用例。

6.1 從大集合中獲取小集合

// 獲取id的集合
List<Long> idList = stockList.stream().map(Stock::getId).collect(Collectors.toList());
// 獲取skuid集合並去重
List<Long> skuIdList = stockList.stream().map(Stock::getSkuId).distinct().collect(Collectors.toList());
// 獲取supplierId集合(supplierId的類型爲int,返回List<Integer>,使用boxed方法裝箱)
Set<Integer> supplierIdSet = stockList.stream().mapToInt(Stock::getSupplierId).boxed().collect(Collectors.toSet());

6.2 分組與分片

// 按skuid分組
Map<Long, List<Stock>> skuIdStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSkuId));
// 過濾supplierId=1而後按skuId分組
Map<Long, List<Stock>> filterSkuIdStockMap = stockList.stream().filter(s -> s.getSupplierId() == 1).collect(Collectors.groupingBy(Stock::getSkuId));
// 按狀態分爲不可用和其餘兩個分片
Map<Boolean, List<Stock>> partitionStockMap = stockList.stream().collect(Collectors.partitioningBy(s -> s.getStatus() == 0));

6.3 計數與求和

// 統計skuId=1的記錄數
long skuIdRecordNum = stockList.stream().filter(s -> s.getSkuId() == 1).count();
// 統計skuId=1的總庫存量
BigDecimal skuIdAmountSum = stockList.stream().filter(s -> s.getSkuId() == 1).map(Stock::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);

6.4 特定用法

// 多重分組並排序,先按supplierId分組,再按skuId分組,排序規則,先supplierId後skuId
Map<Integer, Map<Long, List<Stock>>> supplierSkuStockMap = stockList.stream().collect(Collectors.groupingBy(Stock::getSupplierId, TreeMap::new,
                Collectors.groupingBy(Stock::getSkuId, TreeMap::new, Collectors.toList())));

// 多條件排序,先按supplierId正序排,再按skuId倒序排
// (非stream方法,而是集合的sort方法,直接改變原集合元素,使用Function參數)
stockList.sort(Comparator.comparing(Stock::getSupplierId)
                .thenComparing(Stock::getSkuId, Comparator.reverseOrder()));

參考文檔

http://ifeve.com/stream/ (強烈推薦,很是清晰的介紹)

http://www.infoq.com/cn/articles/java8-new-features-new-stream-api

https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

相關文章
相關標籤/搜索