Java8之Stream流(六)收集

Java8之Stream流(一)基础体验
Java8之Stream流(二)关键知识点
Java8之Stream流(三)缩减操作
Java8之Stream流(四)并行流
Java8之Stream流(五)映射流
Java8之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, A> R collect(Collector<? super T, A, R> 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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>>() {
                                   @Override
                                   public HashSet<HeroPlayerGold> get() {
                                       return new HashSet<>();
                                   }
                               },//第一个参数
                new BiConsumer<HashSet<HeroPlayerGold>, HeroPlayerGold>() {
                    @Override
                    public void accept(HashSet<HeroPlayerGold> heroPlayerGolds, HeroPlayerGold heroPlayerGold) {
                        heroPlayerGolds.add(heroPlayerGold);
                    }
                },//第二个参数
                new BiConsumer<HashSet<HeroPlayerGold>, HashSet<HeroPlayerGold>>() {
                    @Override
                    public 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()方法实现你的自定义收集过程吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • Java流库(java.util.stream) 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图...
    thorhill阅读 4,839评论 0 4
  • 原文地址 http://blog.csdn.net/myherux/article/details/7185511...
    Vissioon阅读 678评论 0 0
  • Int Double Long 设置特定的stream类型, 提高性能,增加特定的函数 无存储。stream不是一...
    patrick002阅读 1,272评论 0 0
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,227评论 1 2
  • 收集器简介 Collector 函数式编程相对于指令式编程的一个主要优势:你只需要指出希望的结果“做什么”,而不用...
    浔它芉咟渡阅读 818评论 0 4