在上一篇文章中,咱們介紹了Stream能夠像操做數據庫同樣來操做集合,可是咱們沒有介紹flatMap和collect操做。這兩種操做對實現複雜的查詢是很是有用的。好比你能夠結果flatMap
和collect
計算stream中的單詞的字符數,像下面代碼那樣。java
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;
Stream<String> words = Stream.of("Java", "Magazine", "is", "the", "best");
Map<String, Long> letterToCount =words.map(w -> w.split(""))
.flatMap(Arrays::stream)
.collect(groupingBy(identity(), counting()));
複製代碼
上述代碼的運行結果是:數據庫
[a:4, b:1, e:3, g:1, h:1, i:2, ..]
複製代碼
這篇文章將會介紹flatMap和collect這兩種操做的更多細節。數組
假設你在一個文章中查找一個單詞,你會怎麼作?微信
咱們可使用Files.lines()
方法,由於它能夠返回一個文章一行一行信息組成的stream。咱們可使用map()
把文章的每行分割是不少單詞,最後,使用`distinct()``移除重複的。咱們將想法轉化爲代碼:ide
Files.lines(Paths.get("stuff.txt"))
.map(line -> line.split("\\s+"))
.distinct() // Stream<String[]>
.forEach(System.out::println);
複製代碼
很不幸,這樣並不正確。若是你運行獲得這樣的結果:函數
[Ljava.lang.String;@7cca494b
[Ljava.lang.String;@7ba4f24f
…
複製代碼
到底發生了什麼事呢?問題出在使用的lambda表達式將會把文件的每行轉化成一個字符串數組(String[])。這就致使map返回的是一個Stream<String[]>類型的結果,咱們實際上須要的是一個Stream類型的結果。post
咱們須要一串的單詞,而不是一串的數組。對於數組可使用Arrays.stream()
將數組變成一個stream。看下面的實現:spa
String[] arrayOfWords = {"Java", "Magazine"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
複製代碼
若是咱們使用下面方式的話其實還有不起做用的,這是由於使用map(Arrays::stream)
後返回的實際上是Stream<Stream>類型。code
Files.lines(Paths.get("stuff.txt"))
.map(line -> line.split("\\s+")) // Stream<String[]>
.map(Arrays::stream) // Stream<Stream<String>>
.distinct() // Stream<Stream<String>>
.forEach(System.out::println);
複製代碼
咱們可使用flatMap來解決這種問題,像下面這樣。使用flatMap方法的做用是返回的是stream中的內容而不是一個stream。cdn
Files.lines(Paths.get("stuff.txt"))
.map(line -> line.split("\\s+")) // Stream<String[]>
.flatMap(Arrays::stream) // Stream<String>
.distinct() // Stream<String>
.forEach(System.out::println);
複製代碼
咱們來具體看一下collect操做。上面文章中看到了返回stream的操做(說明該操做是一箇中間操做)和返回一個值、boolean型值、int型值和Optional型值的操做(說明該操做是終結操做) 。
使用toSet()你能夠把一個stream轉化成一個不包含重複項的集合。下面的代碼展現了怎麼生成高消費(單筆交易>1000$)城市的集合。
Set<String> cities = transactions.stream()
.filter(t -> t.getValue() > 1000)
.map(Transaction::getCity)
.collect(toSet());
複製代碼
注意這樣你不能保證返回什麼類型的Set,你可使用toCollection()來提升可控性。好比你能夠像下面代碼這樣將一個HashSet的構造方法做爲參數。
Set<String> cities = transactions.stream()
.filter(t -> t.getValue() > 1000)
.map(Transaction::getCity)
.collect(toCollection(HashSet::new));
複製代碼
collect操做方法不止這些,上面介紹的只是很小一部分,還能夠實現這些功能:
經過貨幣類型進行分組,計算各類獲取類型的交易總金額(將會返回一個 Map<Currency, Integer>)
將全部交易分類兩組:大金額的和非大金額的(將會返回一個Map<Boolean, List>)
建立多級分組,好比先根據城市分組,而後再根據是否爲大金額交易分組( 將會返回一個Map<String, Map<Boolean, List>>)
讓咱們看一下Stream API和集合器怎麼實現這些查詢,咱們先對一個stream中的數據進行計算平均值,最大值和最小值。接下來咱們再看若是實現簡單的分組,最後咱們咱們將多個集合器放在一塊兒實現強大的查詢功能,好比多級分組。
有不少預約義的集合器和是很方便的使用,好比使用counting() 計算個數:
long howManyTransactions = transactions.stream().collect(counting());
複製代碼
你能夠對Double, Int, 或者Long屬性的元素進行 summing Double(), summingInt(), and summingLong() 操做,像下面這樣:
int totalValue = transactions.stream().collect(summingInt(Transaction::getValue));
複製代碼
相似的你還可使用averagingDouble(), averagingInt(), and averagingLong() 計算平均值,像下面這樣:
double average = transactions.stream().collect(averagingInt(Transaction::getValue));
複製代碼
還能夠經過使用maxBy()和minBy()計算元素中的最大值和最小值,不過你須要定義一個作比較的 比較器,因此maxBy和minBy須要一個Comparator對象最爲參數:
下面的例子中咱們使用了靜態方法comparing(),它將根據傳遞進去的參數生成一個Comparator對象。這個方法根據提取stream中元素的能夠作比較的key來作判斷。在這個例子中是經過銀行交易的金額大小來作比較的。
Optional<Transaction> highestTransaction = transactions.stream()
.collect(maxBy(comparing(Transaction::getValue)));
複製代碼
還有一個叫reducing()的集合器,它能夠經過重複地對stream中的全部元素進行一種操做指導產生一個結果。它和reduce()有點相似。好比下面的代碼使用reducing()方法計算交易的總金額。
int totalValue = transactions.stream().collect(reducing(0, Transaction::getValue, Integer::sum));
複製代碼
reducing() 有三個參數:
一個常規的數據庫操做就是根據一個屬性對數據進行分組。好比根據貨幣對交易進行分組,若是使用迭代那簡直太複雜了:
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for(Transaction transaction : transactions) {
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
複製代碼
Java 8 中有一個叫groupingBy()
的集合器,咱們能夠像這樣作查詢:
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
複製代碼
groupingBy() 方法有一個提取分類key的函數作參數,咱們能夠叫它爲分類函數。在這個例子中咱們使用的是Transaction::getCurrency來實現根據貨幣分組。
還有一個叫作partitioningBy()的函數,這個能夠看作是groupingBy()的特例。它須要一個predicate(返回一個boolean的函數)做爲參數,將會對stream中的元素根據是否知足predicate進行分類。partitioning能夠將stream變成一個 Map<Boolean, List>。使用代碼以下:
Map<Boolean, List<Transaction>> partitionedTransactions =transactions.stream().collect(partitioningBy( t -> t.getValue() > 1000));
複製代碼
若是要要對不一樣貨幣的金額進行求和操做在SQL中能夠結合使用SUM和GROUP BY。那咱們使用Stream API也能這麼作嗎?固然能夠了,像下面這樣使用:
Map<String, Integer> cityToSum = transactions.stream()
.collect(groupingBy(Transaction::getCity, summingInt(Transaction::getValue)));
複製代碼
以前使用的groupingBy (Transaction::getCity)實際上是groupingBy (Transaction::getCity, toList())的速寫方式。
再看一個例子,若是你要統計每一個城市的交易最大值,能夠作這樣實現:
Map<String, Optional<Transaction>> cityToHighestTransaction =
transactions.stream().collect(groupingBy(
Transaction::getCity, maxBy(comparing(Transaction::getValue))));
複製代碼
再看一個更加複雜的例子,在剛纔的例子中咱們給groupingBy傳遞了另一個集合器做爲參數來進一步對元素進行分組。因爲groupingBy自己是一個集合器,咱們能夠經過傳遞其餘groupingBy集合器來建立多級分組,被傳遞進來的這個groupingBy定義了一個二級標準能夠對stream中的元素進行再分組。
下面代碼中咱們先對城市進行分組,而後咱們再根據每一個城市的交易不一樣貨幣的平均值進行分組
Map<String, Map<Currency, Double>> cityByCurrencyToAverage =
transactions.stream().collect(groupingBy(Transaction::getCity,groupingBy(Transaction::getCurrency, averagingInt(Transaction::getValue))));
複製代碼
咱們看到的這些集合器都實現了java.util.stream .Collector
接口。這就意味着你能夠自定義集合器。
這篇文章中,咱們探索了兩個Stream API的高級操做:flatMap和colelct。經過這兩個操做你能夠建立更加複雜的數據處理查詢。
咱們還經過collect方法實現了summarizing, grouping, 和 partitioning 操做。這些操做還能夠被結合起來建立更加複雜的查詢。
感謝閱讀,有興趣能夠關注微信公衆帳號獲取最新推送文章。