跟我学 Java 8 新特性之 Stream 流(六)收集

我们前面的五篇文章基本都是在说将一个集合转成一个流,然后对流进行操作,其实这种操作是最多的,但有时候我们也是需要从流中收集起一些元素,并以集合的方式返回,我们把这种反向操作称为收集。
流API也给我们提供了相应的方法。

如何在流中使用收集功能?

我们先看一看流API给我们提供的方法:
  1. publicinterfaceStream<T> extendsBaseStream<T, Stream<T>> {
  2. //...忽略那些不重要的东西
  3. <R> R collect(Supplier<R> supplier,
  4.                  BiConsumer<R, ? super T> accumulator,
  5.                  BiConsumer<R, R> combiner);
  6. <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包中的声明,它的容颜是这样的:
  1. package java.util.stream;
  2. publicinterfaceCollector<T, A, R> {
  3.      Supplier<A> supplier();
  4.      BiConsumer<A, T> accumulator();
  5.      BinaryOperator<A> combiner();
  6.      Function<A, R> finisher();
  7. }
其中T、A、R的含义和上面是一样的 其中R指定结果的类型,T指定了调用流的元素类型。内部积累的类型由A指定
但是这一篇我们不实现他们,因为JDK已经给我们提供了很强大的方法了,他们位于 java.util.stream下面的 Collectors类,我们本篇也主要是使用 Collectors来实现收集的功能。
Collectors类是一个最终类,里面提供了大量的静态的收集器方法,借助他,我们基本可以实现各种复杂的功能了。
我们来看一下toList和toSet方法:
  1. publicstatic <T>  Collector<T, ?, List<T>> toList()
  2. publicstatic <T> Collector<T, ?, Set<T>> toSet()
其中 Collectors#toList()返回的收集器可以把流中元素收集到一个List中, Collectors#toSet()返回的收集器可以把流中的元素收集到一个Set中。比如:如果你想把元素收集到List中,你可以这样用, steam.collect(Collectors.toList)
接下来,我们把我们的王者荣耀团队经济例子修改一下,把明星玩家和当前获得的金币数收集到一个List里面,把出场的英雄收集到一个Set里面:
  1. #玩家使用的英雄以及当前获得的金币数
  2. publicclassHeroPlayerGold {
  3.    /** 使用的英雄名字 */
  4.    privateString hero;
  5.    /** 玩家的ID */
  6.    privateString player;
  7.    /** 获得的金币数 */
  8.    privateint gold;
  9.    publicHeroPlayerGold(String hero, String player, int gold) {
  10.        this.hero = hero;
  11.        this.player = player;
  12.        this.gold = gold;
  13.    }
  14.    @Override
  15.    publicString toString() {
  16.        return"HeroPlayerGold{" +
  17.                "hero='" + hero + ''' +
  18.                ", player='" + player + ''' +
  19.                ", gold=" + gold +
  20.                '}';
  21.    }
  22. //省略get/set
  23. }
  24. #出场的英雄
  25. publicclassHero {
  26.    /** 使用的英雄名字 */
  27.    privateString hero;
  28.    publicHero(String hero) {
  29.        this.hero = hero;
  30.    }
  31.    @Override
  32.    publicString toString() {
  33.        return"Hero{" +
  34.                "hero='" + hero + ''' +
  35.                '}';
  36.    }
  37. //省略get/set
  38. }
  39. #测试类
  40. publicclassMain {
  41.    publicstaticvoid main(String[] args) {
  42.        learnCollect();
  43.    }
  44.    privatestaticvoid learnCollect() {
  45.        List<HeroPlayerGold> lists = newArrayList<>();
  46.        lists.add(newHeroPlayerGold("盖伦", "RNG-Letme", 100));
  47.        lists.add(newHeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
  48.        lists.add(newHeroPlayerGold("露娜", "RNG-MLXG", 300));
  49.        lists.add(newHeroPlayerGold("狄仁杰", "RNG-UZI", 500));
  50.        lists.add(newHeroPlayerGold("牛头", "RNG-Ming", 500));
  51.        List<PlayerGold> playerGolds = lists.stream()
  52.                .map(plary -> newPlayerGold(plary.getPlayer(), plary.getGold()))
  53.                .collect(Collectors.toList());
  54.        System.out.println("============PlayerGold begin==============");
  55.        playerGolds.forEach(System.out::println);
  56.        System.out.println("============PlayerGold end================n");
  57.        Set<Hero> heroes = lists.stream().map(player -> newHero(player.getHero())).collect(Collectors.toSet());
  58.        System.out.println("============Hero begin==============");
  59.        heroes.forEach(System.out::println);
  60.        System.out.println("============Hero end================");
  61.    }
  62. }
输出的日志:
  1. ============PlayerGoldbegin==============
  2. PlayerGold{player='RNG-Letme', gold=100}
  3. PlayerGold{player='RNG-Xiaohu', gold=300}
  4. PlayerGold{player='RNG-MLXG', gold=300}
  5. PlayerGold{player='RNG-UZI', gold=500}
  6. PlayerGold{player='RNG-Ming', gold=500}
  7. ============PlayerGoldend================
  8. ============Herobegin==============
  9. Hero{hero='露娜'}
  10. Hero{hero='牛头'}
  11. Hero{hero='盖伦'}
  12. Hero{hero='狄仁杰'}
  13. Hero{hero='诸葛亮'}
  14. ============Heroend================
看到这里,大家有感受到流API的威力了吗?提示一下,封装一个工具类,然后结合一FastJson这种东西一起使用!是真的好用啊!其实将数据从集合移到流中,或者将数据从流移回集合的能力,是流API给我们提供的一个强大特性,因为这允许通过流来操作集合,然后把流重新打包成集合。此外,条件合适的时候,让流操作并行发生,提高效率。
接下来我们分析第二个方法,
  1. <R> R collect(Supplier<R> supplier,
  2.                  BiConsumer<R, ? superT> accumulator,
  3.                  BiConsumer<R, R> combiner);
我们第二个版本的收集方法,主要是可以在收集的过程中,给予更多的控制。其中supplier指定如何创建用于保存结果的对象,比如,要使用ArrayList作为结果的集合,需要指定它的构造函数,accumulator函数是将一个元素添加到结果中,而combiner函数合并两个部分的结果。
大家应该发现了吧,他的工作方式和我们第三篇介绍缩减操作时的reduce方法是很像的。它们都必须是无状态和不干预的,并且必须有关联性,三个约束条件缺一不可。
Supplier也是 java.util.function包中的一个函数式接口:
  1. @FunctionalInterface
  2. publicinterfaceSupplier<T> {
  3.    T get();
  4. }
只有一个get(),并且是没有参数的,在collect()方法返回一个R类型的对象,并且get()方法返回一个指向集合的引用。
而accumulator,combiner的类型是 BiConsumer,他们也是 java.util.function包中的一个函数式接口:
  1. @FunctionalInterface
  2. publicinterfaceBiConsumer<T, U> {
  3.    void accept(T t, U u);
  4. }
其中t,u执行某种类型的操作,对于accumulator来说,t指定了目标集合,u指定了要添加到该集合的元素。对于combiner来说,t和u指定的是两个要被合并的集合。
我们把前面的例子改变一下,然后也详细地说一下,在没有用lambda和使用lambda之后的区别:
这个是没有使用lambda前的:
  1. privatestaticvoid learnCollect() {
  2.        List<HeroPlayerGold> lists = newArrayList<>();
  3.        lists.add(newHeroPlayerGold("盖伦", "RNG-Letme", 100));
  4.        lists.add(newHeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
  5.        lists.add(newHeroPlayerGold("露娜", "RNG-MLXG", 300));
  6.        lists.add(newHeroPlayerGold("狄仁杰", "RNG-UZI", 500));
  7.        lists.add(newHeroPlayerGold("牛头", "RNG-Ming", 500));
  8.        lists.stream().collect(newSupplier<HashSet<HeroPlayerGold>>() {
  9.                                   @Override
  10.                                   publicHashSet<HeroPlayerGold> get() {
  11.                                       returnnewHashSet<>();
  12.                                   }
  13.                               },//第一个参数
  14.                newBiConsumer<HashSet<HeroPlayerGold>, HeroPlayerGold>() {
  15.                    @Override
  16.                    publicvoid accept(HashSet<HeroPlayerGold> heroPlayerGolds, HeroPlayerGold heroPlayerGold) {
  17.                        heroPlayerGolds.add(heroPlayerGold);
  18.                    }
  19.                },//第二个参数
  20.                newBiConsumer<HashSet<HeroPlayerGold>, HashSet<HeroPlayerGold>>() {
  21.                    @Override
  22.                    publicvoid accept(HashSet<HeroPlayerGold> heroPlayerGolds, HashSet<HeroPlayerGold> heroPlayerGolds2) {
  23.                        heroPlayerGolds.addAll(heroPlayerGolds2);
  24.                    }
  25.                }//第三个参数
  26.        ).forEach(System.out::println);
  27.    }
在没有使用lambda前,虽然看起来的让人眼花缭乱的,但不得不说,他其实能帮助我们实现非常强大的功能,我们自定义的收集过程,全部都可以交给这个家伙,我们用lambda整理一下:
  1. privatestaticvoid learnCollect() {
  2.        List<HeroPlayerGold> lists = newArrayList<>();
  3.        lists.add(newHeroPlayerGold("盖伦", "RNG-Letme", 100));
  4.        lists.add(newHeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
  5.        lists.add(newHeroPlayerGold("露娜", "RNG-MLXG", 300));
  6.        lists.add(newHeroPlayerGold("狄仁杰", "RNG-UZI", 500));
  7.        lists.add(newHeroPlayerGold("牛头", "RNG-Ming", 500));
  8.        lists.stream().collect(() -> newHashSet<>(),
  9.                                (set,elem)->set.add(elem),
  10.                                (setA,setB)->setA.addAll(setB)
  11.        ).forEach(System.out::println);
  12. }
大家以为到这里就结束了吗?其实还可以使用方法引用和构造函数引用来简化:
  1. privatestaticvoid learnCollect() {
  2.        List<HeroPlayerGold> lists = newArrayList<>();
  3.        lists.add(newHeroPlayerGold("盖伦", "RNG-Letme", 100));
  4.        lists.add(newHeroPlayerGold("诸葛亮", "RNG-Xiaohu", 300));
  5.        lists.add(newHeroPlayerGold("露娜", "RNG-MLXG", 300));
  6.        lists.add(newHeroPlayerGold("狄仁杰", "RNG-UZI", 500));
  7.        lists.add(newHeroPlayerGold("牛头", "RNG-Ming", 500));
  8.        lists.stream().collect(HashSet::new,
  9.                               HashSet::add,
  10.                               HashSet::addAll
  11.        ).forEach(System.out::println);
  12. }

小结一下

本篇带大家入门了Stream的收集操作,但是有了些这入门操作,我相信,你在我的演变过程中已经发现了扩展点了,不管是supplier,accumulator还是combiner,都可以在里面放一些特别的操作进去,从而满足你们的各种要求。
另外一个点,大家一定不要忘记了Collectors这个最终类,里面已经提供了很多很强大的静态方法,如果你们遇到一些特别的需求,首先要想到的应该是Collectors,如果里面的方法都不能实现你的要求,再考虑通过第二个版本的collect()方法实现你的自定义收集过程吧。
本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
极客文库 » 跟我学 Java 8 新特性之 Stream 流(六)收集

Leave a Reply

欢迎加入「极客文库」,成为原创作者从这里开始!

立即加入 了解更多