Java8面世後,目前工做中使用最多的特性就當是Stream API。Stream API結合lamada表達式帶來的是全新的編程體驗,以往一些繁瑣的數據處理,現在不只條理清晰,並且代碼量至少減小了一半。Stream API使用一段時間後,我就不知足於零碎的一些經常使用組合方式,但願對Stream有一個輪廓性的認識。於是在參考了官方文檔和衆多網上資料後,本身對Stream API作了一些小總結,以期達到梳理和記錄之用。java
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,聚合Stream。dom
下面就依次分析下這三個部分ide
經常使用的建立Stream的方式有三種:函數
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方法用來去重,即將一組整數去重後打印出來
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();
Java也在其餘可能有Stream操做的類中增長了便捷的方法
// 文件讀取 java.io.BufferedReader.lines() // 文件遍歷 java.nio.file.Files.walk() // 隨機數 java.util.Random.ints() // 正則匹配 Pattern.splitAsStream(java.lang.CharSequence)
轉換Stream就是把一個Stream經過某些行爲轉換成新的Stream。Stream接口中定義了經常使用的幾個轉換方法,但在實際使用中確實大大的方便。
對於Stream中包含的元素進行去重(依賴元素的equals方法)
以前對一個List進行去重,都是藉助HashSet來作
List list2 = new ArrayList(new HashSet(list));
如今也可使用distinct來作
List list2 = list.stream().distinct().collect(Collectors.toList());
對Stream中包含的元素使用給定的過濾函數進行過濾,新生成的Stream只包含符合條件的元素
List<Integer> numbers = Arrays.asList(-1, 1, 0); numbers.stream().filter(n -> n > 0).collect(Collectors.counting());
對Stream中包含的元素使用給定的轉換函數進行轉換,新生成的Stream只包含轉換生成的元素
Stream.of(1, 2, 3).map(n -> n + 1).collect(Collectors.toList());
這個方法由三個對於原始數據類型的變種方法,分別是mapToInt,mapToLong, mapToDouble, 主要是減小自動裝箱和拆箱的性能消耗。
和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);
生成一個包含原Stream全部元素的新Stream,同時提供一個消費函數(Consume),當Stream中每一個元素被消費時都會執行給定的消費函數。
舉個例子來講,對1到9求和,求和前打印全部求和元素。由於Stream是單向的,作了求和就不能再打印了,怎麼辦呢?用peek方法。
int sum = IntStream.range(1, 10).peek(System.out::println).sum(); System.out.println("sum:" + sum);
對Stream進行截斷操做,獲取其前N個元素,若是原Stream中包含元素個數小於N,就獲取其全部元素。
IntStream.range(1, 10).limit(5).forEach(System.out::println);
丟棄Stream前N個元素,返回剩下元素組成的新Stream,若是原Stream中包含的元素個數小於N,返回空Stream。
好比打印一個數組的第10到20個元素
IntStream.range(1, 100).skip(10).limit(10).forEach(System.out::println);
對Stream中的元素按默認方式排序或者指定比較器排序
Stream.of(1, 3, 4 ,2).sorted().forEach(System.out::println);
聚合操做接受一個元素序列做爲輸入,反覆使用某個合併操做,把序列中的元素合併成一個彙總的結果。好比求元素的總和或最大值,或者把元素累積成一個List對象。Stream接口有一些通用的匯聚操做,好比reduce和collect;也有特定用途的匯聚操做,好比sum,max,count。
將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中還有其餘很便捷的方法,有興趣的能夠研究下。
上面說的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,min方法須要提供Compartor比較器。若是是在諸如mapToInt,mapToLong,mapToDouble之類的基礎數據操做後,則不用提供比較器,另外還支持求和(sum)和平均(average)方法。
users.stream().mapToInt(User::getAge).sum(); users.stream().mapToInt(User::getAge).average();
Stream API還提供了幾種快捷的搜索方法,支持在一組元素中的經常使用搜索。
將Stream中的元素逐個按照指定方式進行消費。
打印全部用戶
users.stream().forEach(System.out::println);
以庫存實體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的用例。
// 獲取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());
// 按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));
// 統計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);
// 多重分組並排序,先按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/