java8流的另一些關於數據處理的具體示例

最近的一段時間,工作上比較忙碌,整個團隊已經996一個月了,記得以前自己還有點希望領導強制加班,自己可以多敲代碼,避免自己浪費太多時間在無意義的事情上,還希望能夠靠強制加班來讓自己務正業。

目前來看,還是自己太年輕了,既然會強制加班,那就是項目上有緊急的事情,是部門需要拿出些成績的時候,工期自然而然的安排的滿滿的,一個人當做兩個人用,做完這個還有那個,終於體驗到每天的生活只有代碼的感覺,而且早上起來還是能明顯感受到疲憊的,不想上班的念頭一直縈繞在腦海,說到這裡,相信每個人都是滿滿的不想上班。不多說了,大家簡單的看下文章吧。

flatMap

首先拋出一個問題,給你一張單詞表,裡面會有幾個單詞,現在的需求就是你把這些單詞用到的字母找到,當然了,不包括重複的。

首先,你可能會這麼做,

Arrays.stream(words)
.map(word ->word.split(""))
.distinct()
.collect(Collectors.toList());

這樣你可能覺得是對的,但是當你運行之後,你才會發現,並不是你想要的那種,因為在map中傳遞的Lambda 為每個單詞返回的是一個String [] ,所有得到的集合是一個裝滿了 String[]的。

java8中給我們提供了一個方法,就是 flatMap,接下來,我們看

Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());

使用這個方法的效果,就是可以將每個生成的流合併為一個流,也就是扁平化,在說明一下,flatMap方法讓你把一個流中的每一個值都換成另一個流,然後把所有的流鏈接起來。

example

下面提一個問題:

給定兩個數組列表,[1,2,3]和[3,4],要求返回一個所有的數組對,也就是說是這樣的:[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)],相信不用java8的新特性,每個人都可以寫的出來,用java8應該怎麼寫呢?

 Integer[] arr1 = {1, 2, 3};
Integer[] arr2 = {3, 4};
Arrays.stream(arr1)
.flatMap(a -> Arrays.stream(arr2)
.map(b -> new Integer [] {a,b}))
.collect(Collectors.toList());

數值相關

java8還引入了三個原始類型特化流接口,IntStream、DoubleStream和 LongStream,分別將流中的元素特化為int、long和double,從而避免了暗含的裝箱成本。常用方式是mapToInt、mapToDouble和mapToLong,執行這幾個方法過後,他們會返回一個特化流,而不是Stream

,而是IntStream,DoubleStream等等。

數值範圍

在和數值打交道的同時,我們通常會有數值範圍的概念,比如你想生成從1到100之間的所有數字。java8引入了兩個可以用於IntStream和LongStream的靜態方法,range和rangeClosed,這兩個方法都是接受第一個起始值,第二個是結束值,後者是包含結束值的。

long count = IntStream
.rangeClosed(1, 100).filter(i -> i % 2 == 0).count();

創建流的幾種方式

接下來,總結下生成創建流的幾種方式:

  • 靜態方法:Stream.of
Stream<string> stream = Stream.of("Welcome ", "Follow ", "me ");
/<string>
  • 數組創建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
  • 由文件生成流(java.nio.file.Files中的很多靜態方法都會返回一個流)
long uniquesWords = 0l;
try (Stream<string> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniquesWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
} catch (IOException e) {
e.printStackTrace();
}
/<string>
  • 由函數生成的流 Stream API中提供了兩個靜態方法來從函數生成流,Stream.iterate 和 Stream.generate
//迭代
Stream.iterate(0, n -> n +2).limit(10).forEach(System.out::println);
//生成
Stream.generate(Math::random).limit(5).forEach(System.out::println);

第一個iterate方法,接收一個初始值,在每次的基礎上加2,這是形成了一個偶數流,這是一個無限流,所以我們可以用limit方法來限制流中元素的數量,用forEach來消費流,

第二個generate方法,它是接收一個Supplier來提供新的值。

收集器

在每個流的最後都會有一個終端操作,來將這個流裝換為一個彙總的結果,其實傳遞給collect方法的參數是Collector接口的一個實現,也就是給Stream中元素做彙總的方法,比如我們最常用的,toList,groupingBy。

toList也是我們經常使用的,就是將流中的元素彙總到一個集合中。

最大值、最小值

我們在獲取集合中元素的最大,最小值的時候可以用Collectors.maxBy和 Collectors.minBy,我們需要自己創建一個Comparator來作為參數

Comparator<apple> tComparator = Comparator.comparingLong(Apple::getWeight);
Optional<apple> collect = appleAllList.stream().max(tComparator);
/<apple>/<apple>

求和操作

Long allWeight = appleAllList.stream().collect(summingLong(Apple::getWeight));
//平均數
Double collectAvg = appleAllList.stream().collect(averagingLong(Apple::getWeight));

在數據統計中,最大值,最小值,求和,平均值可能你都想得到,這樣一次次的獲取可能不太方便,想著一次操作就可以,你可以使用summarizingInt工廠方法返回的收集器。例如,通過一次summarizing操作你可以就得到以上的結果

LongSummaryStatistics summaryStatistics = appleAllList.stream().collect(summarizingLong(Apple::getWeight));

連接字符串

String nameStr = appleAllList.stream().map(Apple::getName).collect(joining());
//還有一個重載的版本

String nameStr = appleAllList.stream().map(Apple::getName).collect(joining(","));

以上的收集器都是可以用reducing工廠方法定義的歸約過程的特殊情況而已

appleAllList.stream().map(Apple::getWeight).reduce((i, j) -> i + j);
appleAllList.stream().collect(reducing(0,(a1, a2) -> a1.getWeight() > a2.getWeight() ? a1 : a2));

reduce操作需要3個參數:起始值;轉換的函數;一個BinaryOperator,將兩個元素合成一個同類型的值。

分組

groupingBy 方法,簡單理解來說,就是分組,返回的是一個Map。在它之前的分組是這樣的:

List<apple> appleAllList = new ArrayList<>();
Map<string>> appleByColorMap = new HashMap<>();
for (Apple apple : appleAllList) {
String color = apple.getColor();
if (appleByColorMap.get(color) == null) {
List<apple> appList = new ArrayList<>();
appList.add(apple);
appleByColorMap.put(color, appList);
} else {
List<apple> apples = appleByColorMap.get(color);
apples.add(apple);
}
}
/<apple>/<apple>/<string>/<apple>

在有了groupingBy 方法之後就是這樣的,不言自明瞭吧

appleByColorMap = appleAllList.stream().collect(groupingBy(Apple::getColor));

在這裡,你給groupingBy傳遞了一個方法,它是獲取蘋果的顏色的,這個叫分類函數。按照函數的結果來進行分組,函數的結果也就作為了key值。當然了,有時候業務需求上的分組並不向這樣簡單,這個時候我們也可以自定義分組函數:

list.stream().collect(groupingBy(apple -> {
if (apple.getWeight() > 300) {
return "Heavy";
} else if (apple.getWeight() < 200) {
return "little";
} else {
return "normal";
}
}));

ps:(這裡需要強調一下,在開發過程中千萬不要有上面代碼的壞習慣,對於300,200這種要定義言簡意賅的常量去表示,不要寫這種帶有魔法數字,我這裡只是舉個例子,不要關注錯重點哦)

除了自定義分組,我們還會多級分組就是我們可以把一個內層groupingBy傳遞給外層groupingBy,就像下面這樣:

Map<integer>>> appleMap = list.stream().collect(
groupingBy(Apple::getType,
groupingBy(dish -> {
if (dish.getWeight() <= 400)
return "DIET";
else if (dish.getWeight() <= 700)
return "NORMAL";
else return "FAT";
})));
/<integer>

當然了出來的結果也會是兩層Map。

到了這裡,第二個groupingBy是一個收集器,所以說我們也可以傳別的收集器。

 Map<integer> typeNum = list.stream().collect( 

groupingBy(Apple::getType, counting()));
/<integer>

這篇文章是之前自己的筆記,整理出來,畢竟也是很久沒發文了,在此也是對這個公眾號的訂閱用戶表示由衷的謝意,大家能有收穫就好。

如果對本文有任何異議或者說有什麼好的建議,可以加我好友(公眾號後臺聯繫作者),也可以在下面留言區留言。希望以上內容能幫助大家在碼代碼的過程中有所幫助。

這樣的分享我會一直持續,你的關注、轉發和好看是對我最大的支持,感謝。


分享到:


相關文章: