亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

詳解Java8 Collect收集Stream的方法

2024-07-14 08:40:18
字體:
來源:轉載
供稿:網友

Collection, Collections, collect, Collector, Collectos

Collection是Java集合的祖先接口。
Collections是java.util包下的一個工具類,內涵各種處理集合的靜態方法。
java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一個函數,負責收集流。
java.util.stream.Collector 是一個收集函數的接口, 聲明了一個收集器的功能。
java.util.Comparators則是一個收集器的工具類,內置了一系列收集器實現。

收集器的作用

你可以把Java8的流看做花哨又懶惰的數據集迭代器。他們支持兩種類型的操作:中間操作(e.g. filter, map)和終端操作(如count, findFirst, forEach, reduce). 中間操作可以連接起來,將一個流轉換為另一個流。這些操作不會消耗流,其目的是建立一個流水線。與此相反,終端操作會消耗類,產生一個最終結果。collect就是一個歸約操作,就像reduce一樣可以接受各種做法作為參數,將流中的元素累積成一個匯總結果。具體的做法是通過定義新的Collector接口來定義的。

預定義的收集器

下面簡單演示基本的內置收集器。模擬數據源如下:

final ArrayList<Dish> dishes = Lists.newArrayList(    new Dish("pork", false, 800, Type.MEAT),    new Dish("beef", false, 700, Type.MEAT),    new Dish("chicken", false, 400, Type.MEAT),    new Dish("french fries", true, 530, Type.OTHER),    new Dish("rice", true, 350, Type.OTHER),    new Dish("season fruit", true, 120, Type.OTHER),    new Dish("pizza", true, 550, Type.OTHER),    new Dish("prawns", false, 300, Type.FISH),    new Dish("salmon", false, 450, Type.FISH));

最大值,最小值,平均值

// 為啥返回Optional? 如果stream為null怎么辦, 這時候Optinal就很有意義了Optional<Dish> mostCalorieDish = dishes.stream().max(Comparator.comparingInt(Dish::getCalories));Optional<Dish> minCalorieDish = dishes.stream().min(Comparator.comparingInt(Dish::getCalories));Double avgCalories = dishes.stream().collect(Collectors.averagingInt(Dish::getCalories));IntSummaryStatistics summaryStatistics = dishes.stream().collect(Collectors.summarizingInt(Dish::getCalories));double average = summaryStatistics.getAverage();long count = summaryStatistics.getCount();int max = summaryStatistics.getMax();int min = summaryStatistics.getMin();long sum = summaryStatistics.getSum();

這幾個簡單的統計指標都有Collectors內置的收集器函數,尤其是針對數字類型拆箱函數,將會比直接操作包裝類型開銷小很多。

連接收集器

想要把Stream的元素拼起來?

//直接連接String join1 = dishes.stream().map(Dish::getName).collect(Collectors.joining());//逗號String join2 = dishes.stream().map(Dish::getName).collect(Collectors.joining(", "));

toList

List<String> names = dishes.stream().map(Dish::getName).collect(toList());

將原來的Stream映射為一個單元素流,然后收集為List。

toSet

Set<Type> types = dishes.stream().map(Dish::getType).collect(Collectors.toSet());

將Type收集為一個set,可以去重復。

toMap

Map<Type, Dish> byType = dishes.stream().collect(toMap(Dish::getType, d -> d));

有時候可能需要將一個數組轉為map,做緩存,方便多次計算獲取。toMap提供的方法k和v的生成函數。(注意,上述demo是一個坑,不可以這樣用?。。? 請使用toMap(Function, Function, BinaryOperator))

上面幾個幾乎是最常用的收集器了,也基本夠用了。但作為初學者來說,理解需要時間。想要真正明白為什么這樣可以做到收集,就必須查看內部實現,可以看到,這幾個收集器都是基于java.util.stream.Collectors.CollectorImpl,也就是開頭提到過了Collector的一個實現類。后面自定義收集器會學習具體用法。

自定義歸約reducing

前面幾個都是reducing工廠方法定義的歸約過程的特殊情況,其實可以用Collectors.reducing創建收集器。比如,求和

Integer totalCalories = dishes.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));//使用內置函數代替箭頭函數Integer totalCalories2 = dishes.stream().collect(reducing(0, Dish::getCalories, Integer::sum));

當然也可以直接使用reduce

Optional<Integer> totalCalories3 = dishes.stream().map(Dish::getCalories).reduce(Integer::sum);

雖然都可以,但考量效率的話,還是要選擇下面這種

int sum = dishes.stream().mapToInt(Dish::getCalories).sum();

根據情況選擇最佳方案

上面的demo說明,函數式編程通常提供了多種方法來執行同一個操作,使用收集器collect比直接使用stream的api用起來更加復雜,好處是collect能提供更高水平的抽象和概括,也更容易重用和自定義。

我們的建議是,盡可能為手頭的問題探索不同的解決方案,始終選擇最專業的一個,無論從可讀性還是性能來看,這一般都是最好的決定。

reducing除了接收一個初始值,還可以把第一項當作初始值

Optional<Dish> mostCalorieDish = dishes.stream()        .collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

reducing

關于reducing的用法比較復雜,目標在于把兩個值合并成一個值。

public static <T, U>  Collector<T, ?, U> reducing(U identity,                Function<? super T, ? extends U> mapper,                BinaryOperator<U> op) 

首先看到3個泛型,

U是返回值的類型,比如上述demo中計算熱量的,U就是Integer。

關于T,T是Stream里的元素類型。由Function的函數可以知道,mapper的作用就是接收一個參數T,然后返回一個結果U。對應demo中Dish。

?在返回值Collector的泛型列表的中間,這個表示容器類型,一個收集器當然需要一個容器來存放數據。這里的?則表示容器類型不確定。事實上,在這里的容器就是U[]。

關于參數:

identity是返回值類型的初始值,可以理解為累加器的起點。

mapper則是map的作用,意義在于將Stream流轉換成你想要的類型流。

op則是核心函數,作用是如何處理兩個變量。其中,第一個變量是累積值,可以理解為sum,第二個變量則是下一個要計算的元素。從而實現了累加。

reducing還有一個重載的方法,可以省略第一個參數,意義在于把Stream里的第一個參數當做初始值。

public static <T> Collector<T, ?, Optional<T>>  reducing(BinaryOperator<T> op) 

先看返回值的區別,T表示輸入值和返回值類型,即輸入值類型和輸出值類型相同。還有不同的就是Optional了。這是因為沒有初始值,而第一個參數有可能是null,當Stream的元素是null的時候,返回Optional就很意義了。

再看參數列表,只剩下BinaryOperator。BinaryOperator是一個三元組函數接口,目標是將兩個同類型參數做計算后返回同類型的值??梢园凑?>2? 1:2來理解,即求兩個數的最大值。求最大值是比較好理解的一種說法,你可以自定義lambda表達式來選擇返回值。那么,在這里,就是接收兩個Stream的元素類型T,返回T類型的返回值。用sum累加來理解也可以。

上述的demo中發現reduce和collect的作用幾乎一樣,都是返回一個最終的結果,比如,我們可以使用reduce實現toList效果:

//手動實現toListCollector --- 濫用reduce, 不可變的規約---不可以并行List<Integer> calories = dishes.stream().map(Dish::getCalories)    .reduce(new ArrayList<Integer>(),        (List<Integer> l, Integer e) -> {          l.add(e);          return l;        },        (List<Integer> l1, List<Integer> l2) -> {          l1.addAll(l2);          return l1;        }    );

關于上述做法解釋一下。

<U> U reduce(U identity,         BiFunction<U, ? super T, U> accumulator,         BinaryOperator<U> combiner);

U是返回值類型,這里就是List

BiFunction<U, ? super T, U> accumulator是是累加器,目標在于累加值和單個元素的計算規則。這里就是List和元素做運算,最終返回List。即,添加一個元素到list。

BinaryOperator<U> combiner是組合器,目標在于把兩個返回值類型的變量合并成一個。這里就是兩個list合并。
這個解決方案有兩個問題:一個是語義問題,一個是實際問題。語義問題在于,reduce方法旨在把兩個值結合起來生成一個新值,它是一個不可變歸約。相反,collect方法的設計就是要改變容器,從而累積要輸出的結果。這意味著,上面的代碼片段是在濫用reduce方法,因為它在原地改變了作為累加器的List。錯誤的語義來使用reduce方法還會造成一個實際問題:這個歸約不能并行工作,因為由多個線程并發修改同一個數據結構可能會破壞List本身。在這種情況下,如果你想要線程安全,就需要每次分配一個新的List,而對象分配又會影響性能。這就是collect適合表達可變容器上的歸約的原因,更關鍵的是它適合并行操作。

總結:reduce適合不可變容器歸約,collect適合可變容器歸約。collect適合并行。

分組

數據庫中經常遇到分組求和的需求,提供了group by原語。在Java里, 如果按照指令式風格(手動寫循環)的方式,將會非常繁瑣,容易出錯。而Java8則提供了函數式解法。

比如,將dish按照type分組。和前面的toMap類似,但分組的value卻不是一個dish,而是一個List。

Map<Type, List<Dish>> dishesByType = dishes.stream().collect(groupingBy(Dish::getType));

這里

public static <T, K> Collector<T, ?, Map<K, List<T>>>  groupingBy(Function<? super T, ? extends K> classifier)

參數分類器為Function,旨在接收一個參數,轉換為另一個類型。上面的demo就是把stream的元素dish轉成類型Type,然后根據Type將stream分組。其內部是通過HashMap來實現分組的。groupingBy(classifier, HashMap::new, downstream);

除了按照stream元素自身的屬性函數去分組,還可以自定義分組依據,比如根據熱量范圍分組。

既然已經知道groupingBy的參數為Function, 并且Function的參數類型為Dish,那么可以自定義分類器為:

private CaloricLevel getCaloricLevel(Dish d) {  if (d.getCalories() <= 400) {   return CaloricLevel.DIET;  } else if (d.getCalories() <= 700) {   return CaloricLevel.NORMAL;  } else {   return CaloricLevel.FAT;  }}

再傳入參數即可

Map<CaloricLevel, List<Dish>> dishesByLevel = dishes.stream()    .collect(groupingBy(this::getCaloricLevel));

多級分組

groupingBy還重載了其他幾個方法,比如

public static <T, K, A, D>  Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,                     Collector<? super T, A, D> downstream)

泛型多的恐怖。簡單的認識一下。classifier還是分類器,就是接收stream的元素類型,返回一個你想要分組的依據,也就是提供分組依據的基數的。所以T表示stream當前的元素類型,K表示分組依據的元素類型。第二個參數downstream,下游是一個收集器Collector. 這個收集器元素類型是T的子類,容器類型container為A,reduction返回值類型為D。也就是說分組的K通過分類器提供,分組的value則通過第二個參數的收集器reduce出來。正好,上個demo的源碼為:

public static <T, K> Collector<T, ?, Map<K, List<T>>>  groupingBy(Function<? super T, ? extends K> classifier) {    return groupingBy(classifier, toList());  }

將toList當作reduce收集器,最終收集的結果是一個List<Dish>, 所以分組結束的value類型是List<Dish>。那么,可以類推value類型取決于reduce收集器,而reduce收集器則有千千萬。比如,我想對value再次分組,分組也是一種reduce。

//多級分組Map<Type, Map<CaloricLevel, List<Dish>>> byTypeAndCalory = dishes.stream().collect(  groupingBy(Dish::getType, groupingBy(this::getCaloricLevel)));byTypeAndCalory.forEach((type, byCalory) -> { System.out.println("----------------------------------"); System.out.println(type); byCalory.forEach((level, dishList) -> {  System.out.println("/t" + level);  System.out.println("/t/t" + dishList); });});

驗證結果為:

----------------------------------FISH  DIET    [Dish(name=prawns, vegetarian=false, calories=300, type=FISH)]  NORMAL    [Dish(name=salmon, vegetarian=false, calories=450, type=FISH)]----------------------------------MEAT  FAT    [Dish(name=pork, vegetarian=false, calories=800, type=MEAT)]  DIET    [Dish(name=chicken, vegetarian=false, calories=400, type=MEAT)]  NORMAL    [Dish(name=beef, vegetarian=false, calories=700, type=MEAT)]----------------------------------OTHER  DIET    [Dish(name=rice, vegetarian=true, calories=350, type=OTHER), Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER)]  NORMAL    [Dish(name=french fries, vegetarian=true, calories=530, type=OTHER), Dish(name=pizza, vegetarian=true, calories=550, type=OTHER)]

總結:groupingBy的核心參數為K生成器,V生成器。V生成器可以是任意類型的收集器Collector。

比如,V生成器可以是計算數目的, 從而實現了sql語句中的select count(*) from table A group by Type

Map<Type, Long> typesCount = dishes.stream().collect(groupingBy(Dish::getType, counting()));System.out.println(typesCount);-----------{FISH=2, MEAT=3, OTHER=4}

sql查找分組最高分select MAX(id) from table A group by Type

Map<Type, Optional<Dish>> mostCaloricByType = dishes.stream()    .collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));

這里的Optional沒有意義,因為肯定不是null。那么只好取出來了。使用collectingAndThen

Map<Type, Dish> mostCaloricByType = dishes.stream()  .collect(groupingBy(Dish::getType,    collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));

到這里似乎結果出來了,但IDEA不同意,編譯黃色報警,按提示修改后變為:

Map<Type, Dish> mostCaloricByType = dishes.stream()  .collect(toMap(Dish::getType, Function.identity(),    BinaryOperator.maxBy(comparingInt(Dish::getCalories))));

是的,groupingBy就變成toMap了,key還是Type,value還是Dish,但多了一個參數?。∵@里回應開頭的坑,開頭的toMap演示是為了容易理解,真那么用則會被搞死。我們知道把一個List重組為Map必然會面臨k相同的問題。當K相同時,v是覆蓋還是不管呢?前面的demo的做法是當k存在時,再次插入k則直接拋出異常:

java.lang.IllegalStateException: Duplicate key Dish(name=pork, vegetarian=false, calories=800, type=MEAT)  at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)

正確的做法是提供處理沖突的函數,在本demo中,處理沖突的原則就是找出最大的,正好符合我們分組求最大的要求。(真的不想搞Java8函數式學習了,感覺到處都是性能問題的坑)

繼續數據庫sql映射,分組求和select sum(score) from table a group by Type

Map<Type, Integer> totalCaloriesByType = dishes.stream()  .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));

然而常常和groupingBy聯合使用的另一個收集器是mapping方法生成的。這個方法接收兩個參數:一個函數對流中的元素做變換,另一個則將變換的結果對象收集起來。其目的是在累加之前對每個輸入元素應用一個映射函數,這樣就可以讓接收特定類型元素的收集器適應不同類型的對象。我么來看一個使用這個收集器的實際例子。比如你想得到,對于每種類型的Dish,菜單中都有哪些CaloricLevel。我們可以把groupingBy和mapping收集器結合起來,如下所示:

Map<Type, Set<CaloricLevel>> caloricLevelsByType = dishes.stream()  .collect(groupingBy(Dish::getType, mapping(this::getCaloricLevel, toSet())));

這里的toSet默認采用的HashSet,也可以手動指定具體實現toCollection(HashSet::new)

分區

分區是分組的特殊情況:由一個謂詞(返回一個布爾值的函數)作為分類函數,它稱為分區函數。分區函數返回一個布爾值,這意味著得到的分組Map的鍵類型是Boolean,于是它最多可以分為兩組:true or false. 例如,如果你是素食者,你可能想要把菜單按照素食和非素食分開:

Map<Boolean, List<Dish>> partitionedMenu = dishes.stream().collect(partitioningBy(Dish::isVegetarian));

當然,使用filter可以達到同樣的效果:

List<Dish> vegetarianDishes = dishes.stream().filter(Dish::isVegetarian).collect(Collectors.toList());

分區相對來說,優勢就是保存了兩個副本,當你想要對一個list分類時挺有用的。同時,和groupingBy一樣,partitioningBy一樣有重載方法,可以指定分組value的類型。

Map<Boolean, Map<Type, List<Dish>>> vegetarianDishesByType = dishes.stream()  .collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));Map<Boolean, Integer> vegetarianDishesTotalCalories = dishes.stream()  .collect(partitioningBy(Dish::isVegetarian, summingInt(Dish::getCalories)));Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = dishes.stream()  .collect(partitioningBy(Dish::isVegetarian,    collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));

作為使用partitioningBy收集器的最后一個例子,我們把菜單數據模型放在一邊,來看一個更加復雜也更為有趣的例子:將數組分為質數和非質數。

首先,定義個質數分區函數:

private boolean isPrime(int candidate) {  int candidateRoot = (int) Math.sqrt((double) candidate);  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);}

然后找出1到100的質數和非質數

Map<Boolean, List<Integer>> partitionPrimes = IntStream.rangeClosed(2, 100).boxed()  .collect(partitioningBy(this::isPrime));


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
一区二区三区天堂av| 国内揄拍国内精品少妇国语| 在线观看免费高清视频97| 国产成人精品一区二区在线| 欧美性开放视频| 自拍偷拍亚洲欧美| 亚洲欧美中文日韩v在线观看| 在线播放国产精品| 欧美性videos高清精品| 欧美色播在线播放| 欧美电影免费观看高清完整| 国模极品一区二区三区| 91精品国产自产在线| 国产一区二区在线免费| 最近2019中文字幕mv免费看| 亚洲最大福利网| 久久精品一偷一偷国产| 91国偷自产一区二区三区的观看方式| 亚洲精品国精品久久99热一| 91精品国产91久久久久| 亚洲天堂开心观看| 一本一道久久a久久精品逆3p| 国产日韩在线精品av| 日韩av免费一区| 日本精品视频在线| 日韩精品在线电影| 欧美性猛交xxxx| 国产精品都在这里| 国产精品一区=区| 操人视频在线观看欧美| 一本色道久久综合狠狠躁篇的优点| 国产精品美乳在线观看| 亚洲国产日韩一区| 911国产网站尤物在线观看| 97超视频免费观看| 欧美精品第一页在线播放| 国产精品免费网站| 欧美视频中文字幕在线| 亚洲精品久久久一区二区三区| 日韩在线中文视频| 久久久久久久久网站| 欧美精品国产精品日韩精品| 91精品91久久久久久| 日韩成人在线观看| 日韩欧美在线观看| 久久久久久久成人| 青青草国产精品一区二区| 亚洲丝袜一区在线| 亚洲综合中文字幕在线| 国产精品一二三视频| 国产精品99久久久久久www| 亚洲最大的网站| 日韩欧美极品在线观看| 日韩精品极品毛片系列视频| 日韩女优人人人人射在线视频| 亚洲图片欧美日产| 欧美在线欧美在线| 亚洲欧美色婷婷| 中日韩美女免费视频网址在线观看| 狠狠久久亚洲欧美专区| 欧美成人免费播放| 精品国偷自产在线| 国产精品精品久久久久久| 欧美色videos| 亚洲国产欧美一区二区三区久久| 福利视频一区二区| 色综合色综合久久综合频道88| 精品久久久一区| 日韩hd视频在线观看| 亚洲自拍av在线| 久久久久亚洲精品| 精品一区二区三区四区| 91精品国产综合久久香蕉最新版| 欧美黄色片免费观看| 久久免费视频在线观看| 一区二区三区视频免费在线观看| 91在线视频九色| 国产精品v片在线观看不卡| 亚洲乱码国产乱码精品精| 久久天天躁狠狠躁夜夜躁2014| 亚洲跨种族黑人xxx| 欧美一区二区三区免费视| 亚洲一区亚洲二区| 色综合影院在线| 亚洲成人久久网| 欧美成人激情图片网| 色中色综合影院手机版在线观看| 中文字幕亚洲欧美在线| 欧美视频一二三| 午夜精品一区二区三区在线| 国产精品日韩欧美| 538国产精品视频一区二区| 国产精品xxxxx| 性色av一区二区三区红粉影视| 色噜噜狠狠狠综合曰曰曰| 深夜成人在线观看| 国产欧美一区二区三区四区| 国产欧美久久一区二区| 欧美高清一级大片| 日韩精品在线观看视频| 中文字幕欧美在线| 国产精品久在线观看| 日韩国产高清视频在线| 庆余年2免费日韩剧观看大牛| 日韩国产一区三区| www高清在线视频日韩欧美| 欧美日韩亚洲激情| 日韩中文字幕欧美| 久久成人精品一区二区三区| 日韩av电影国产| 日韩精品中文字幕视频在线| 日本午夜精品理论片a级appf发布| 亚洲欧美日韩综合| 成人欧美一区二区三区黑人| 欧美乱妇高清无乱码| 欧美性精品220| 国产精品久久久久久久app| 中文字幕少妇一区二区三区| 亚洲激情中文字幕| 麻豆一区二区在线观看| 亚洲欧洲美洲在线综合| 日韩精品免费综合视频在线播放| 激情懂色av一区av二区av| 欧美精品一区在线播放| 色999日韩欧美国产| 黑人巨大精品欧美一区免费视频| 亚洲性视频网址| 91国产精品91| 久久人人爽人人爽人人片av高清| 欧美日韩爱爱视频| 成人羞羞国产免费| 91福利视频在线观看| 国产精品高潮呻吟久久av黑人| 久久国产加勒比精品无码| 日韩精品在线视频观看| 国产精品一区二区久久国产| 国产精品美女在线| 国产午夜精品免费一区二区三区| 久久久久99精品久久久久| 亚洲精品久久久久中文字幕二区| 国产亚洲aⅴaaaaaa毛片| 欧美激情免费观看| 亚洲电影中文字幕| 国产精品一区二区电影| 国产精品美女在线| 亚洲国产黄色片| 亚洲人永久免费| 欧美精品在线免费观看| 欧美老女人在线视频| 国产精品视频中文字幕91| 日韩av不卡在线| 国模精品视频一区二区三区| 国产一区二中文字幕在线看| 92看片淫黄大片欧美看国产片| 色99之美女主播在线视频| 日韩中文在线中文网三级| 日韩不卡在线观看| 深夜福利亚洲导航| 久久久精品日本| 久久伊人精品视频| 欧美日韩视频免费播放| 亚洲精品不卡在线| 欧美日韩成人免费| 国产偷亚洲偷欧美偷精品|