跟我学 Java 8 新特性之 Stream 流(六)收集
轉載自? ?跟我學 Java 8 新特性之 Stream 流(六)收集
我們前面的五篇文章基本都是在說將一個集合轉成一個流,然后對流進行操作,其實這種操作是最多的,但有時候我們也是需要從流中收集起一些元素,并以集合的方式返回,我們把這種反向操作稱為收集。
流API也給我們提供了相應的方法。
如何在流中使用收集功能?
我們先看一看流API給我們提供的方法:
public interface Stream<T> extends BaseStream<T, Stream<T>> {//...忽略那些不重要的東西<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);<R, A> R collect(Collector<? super T, A, R> collector);}流API中給我們提供了兩種,我給大家分析一下。
R collect(Collector collector);
其中R指定結果的類型,T指定了調用流的元素類型。內部積累的類型由A指定。collectorFunc是一個收集器,指定收集過程如何執行,collect()方法是一個終端方法。
雖然我們基本上很少會用到自定義的collectorFunc,但是了為擴展大家的知識面,我們還是簡單地聊一聊Collector,Because it's my style!
Collector接口位于 java.util.stream包中的聲明,它的容顏是這樣的:
package java.util.stream;public interface Collector<T, A, R> {Supplier<A> supplier();BiConsumer<A, T> accumulator();BinaryOperator<A> combiner();Function<A, R> finisher();}其中T、A、R的含義和上面是一樣的 其中R指定結果的類型,T指定了調用流的元素類型。內部積累的類型由A指定。
但是這一篇我們不實現他們,因為JDK已經給我們提供了很強大的方法了,他們位于 java.util.stream下面的 Collectors類,我們本篇也主要是使用 Collectors來實現收集的功能。
Collectors類是一個最終類,里面提供了大量的靜態的收集器方法,借助他,我們基本可以實現各種復雜的功能了。
我們來看一下toList和toSet方法:
public static <T> ?Collector<T, ?, List<T>> toList()public static <T> Collector<T, ?, Set<T>> toSet()其中 Collectors#toList()返回的收集器可以把流中元素收集到一個List中, Collectors#toSet()返回的收集器可以把流中的元素收集到一個Set中。比如:如果你想把元素收集到List中,你可以這樣用, steam.collect(Collectors.toList)。
接下來,我們把我們的王者榮耀團隊經濟例子修改一下,把明星玩家和當前獲得的金幣數收集到一個List里面,把出場的英雄收集到一個Set里面:
#玩家使用的英雄以及當前獲得的金幣數 public class HeroPlayerGold {/** 使用的英雄名字 */private String hero;/** 玩家的ID */private String player;/** 獲得的金幣數 */private int gold;public HeroPlayerGold(String hero, String player, int gold) {this.hero = hero;this.player = player;this.gold = gold;}@Overridepublic String toString() {return "HeroPlayerGold{" +"hero='" + hero + '\'' +", player='" + player + '\'' +", gold=" + gold +'}';} //省略get/set}#出場的英雄 public class Hero {/** 使用的英雄名字 */private String hero;public Hero(String hero) {this.hero = hero;}@Overridepublic String toString() {return "Hero{" +"hero='" + hero + '\'' +'}';} //省略get/set }#測試類 public class Main {public static void main(String[] args) {learnCollect();}private static void learnCollect() {List<HeroPlayerGold> lists = new ArrayList<>();lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));List<PlayerGold> playerGolds = lists.stream().map(plary -> new PlayerGold(plary.getPlayer(), plary.getGold())).collect(Collectors.toList());System.out.println("============PlayerGold begin==============");playerGolds.forEach(System.out::println);System.out.println("============PlayerGold end================\n");Set<Hero> heroes = lists.stream().map(player -> new Hero(player.getHero())).collect(Collectors.toSet());System.out.println("============Hero begin==============");heroes.forEach(System.out::println);System.out.println("============Hero end================");}}輸出的日志:
============PlayerGold begin==============
PlayerGold{player='RNG-Letme', gold=100}
PlayerGold{player='RNG-Xiaohu', gold=300}
PlayerGold{player='RNG-MLXG', gold=300}
PlayerGold{player='RNG-UZI', gold=500}
PlayerGold{player='RNG-Ming', gold=500}
============PlayerGold end================
?
============Hero begin==============
Hero{hero='露娜'}
Hero{hero='牛頭'}
Hero{hero='蓋倫'}
Hero{hero='狄仁杰'}
Hero{hero='諸葛亮'}
============Hero end================
看到這里,大家有感受到流API的威力了嗎?提示一下,封裝一個工具類,然后結合一FastJson這種東西一起使用!是真的好用啊!其實將數據從集合移到流中,或者將數據從流移回集合的能力,是流API給我們提供的一個強大特性,因為這允許通過流來操作集合,然后把流重新打包成集合。此外,條件合適的時候,讓流操作并行發生,提高效率。
接下來我們分析第二個方法,
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);我們第二個版本的收集方法,主要是可以在收集的過程中,給予更多的控制。其中supplier指定如何創建用于保存結果的對象,比如,要使用ArrayList作為結果的集合,需要指定它的構造函數,accumulator函數是將一個元素添加到結果中,而combiner函數合并兩個部分的結果。
大家應該發現了吧,他的工作方式和我們第三篇介紹縮減操作時的reduce方法是很像的。它們都必須是無狀態和不干預的,并且必須有關聯性,三個約束條件缺一不可。
Supplier也是 java.util.function包中的一個函數式接口:
@FunctionalInterface public interface Supplier<T> {T get(); }只有一個get(),并且是沒有參數的,在collect()方法返回一個R類型的對象,并且get()方法返回一個指向集合的引用。
?
而accumulator,combiner的類型是 BiConsumer,他們也是 java.util.function包中的一個函數式接口:
@FunctionalInterface public interface BiConsumer<T, U> {void accept(T t, U u); }其中t,u執行某種類型的操作,對于accumulator來說,t指定了目標集合,u指定了要添加到該集合的元素。對于combiner來說,t和u指定的是兩個要被合并的集合。
我們把前面的例子改變一下,然后也詳細地說一下,在沒有用lambda和使用lambda之后的區別:
這個是沒有使用lambda前的:
private static void learnCollect() {List<HeroPlayerGold> lists = new ArrayList<>();lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));lists.stream().collect(new Supplier<HashSet<HeroPlayerGold>>() {@Overridepublic HashSet<HeroPlayerGold> get() {return new HashSet<>();}},//第一個參數new BiConsumer<HashSet<HeroPlayerGold>, HeroPlayerGold>() {@Overridepublic void accept(HashSet<HeroPlayerGold> heroPlayerGolds, HeroPlayerGold heroPlayerGold) {heroPlayerGolds.add(heroPlayerGold);}},//第二個參數new BiConsumer<HashSet<HeroPlayerGold>, HashSet<HeroPlayerGold>>() {@Overridepublic void accept(HashSet<HeroPlayerGold> heroPlayerGolds, HashSet<HeroPlayerGold> heroPlayerGolds2) {heroPlayerGolds.addAll(heroPlayerGolds2);}}//第三個參數).forEach(System.out::println);}在沒有使用lambda前,雖然看起來的讓人眼花繚亂的,但不得不說,他其實能幫助我們實現非常強大的功能,我們自定義的收集過程,全部都可以交給這個家伙,我們用lambda整理一下:
private static void learnCollect() {List<HeroPlayerGold> lists = new ArrayList<>();lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));lists.stream().collect(() -> new HashSet<>(),(set,elem)->set.add(elem),(setA,setB)->setA.addAll(setB)).forEach(System.out::println);}大家以為到這里就結束了嗎?其實還可以使用方法引用和構造函數引用來簡化: private static void learnCollect() {List<HeroPlayerGold> lists = new ArrayList<>();lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));lists.stream().collect(HashSet::new,HashSet::add,HashSet::addAll).forEach(System.out::println); }小結一下
本篇帶大家入門了Stream的收集操作,但是有了些這入門操作,我相信,你在我的演變過程中已經發現了擴展點了,不管是supplier,accumulator還是combiner,都可以在里面放一些特別的操作進去,從而滿足你們的各種要求。
另外一個點,大家一定不要忘記了Collectors這個最終類,里面已經提供了很多很強大的靜態方法,如果你們遇到一些特別的需求,首先要想到的應該是Collectors,如果里面的方法都不能實現你的要求,再考慮通過第二個版本的collect()方法實現你的自定義收集過程吧。
總結
以上是生活随笔為你收集整理的跟我学 Java 8 新特性之 Stream 流(六)收集的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CocosCreator之KUOKUO带
- 下一篇: 干货 | 日访问过亿,办公IM及开放式平