前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 8 Stream简介和“复用”的问题

Java 8 Stream简介和“复用”的问题

作者头像
明明如月学长
发布2021-08-27 16:24:22
1.1K0
发布2021-08-27 16:24:22
举报
文章被收录于专栏:明明如月的技术专栏

最近工作后开始使用Stream,用起来比较顺手,可以说已经“沉浸于Stream无法自拔”,很少再用foreach循环了。

其中的Collectors.toMap 和 Collectors.groupingBy等操作好用到爆。

但是纠结于“Stream复用”问题。

看了一些文章如(https://blog.csdn.net/yiifaa/article/details/78118342)写得不是很清楚,

这里简单整理一下。

参考资料  :《Java 8 in Action: Lambdas, streams, and functional-style programming

本文先对Stream作基本介绍,然后介绍如何“复用”stream。

1、 基本介绍

Stream两种操作

[1] filter,map,和limit组合形成管道

[2] collect操作触发管道的执行和stream的关闭

前一种成为 中间操作(intermediate operations) ,后面称之为 终端操作(terminal operations)。

中间操作的特性

中间操作是属于“懒性”的,直到终端操作才执行处理操作。因为中间操作经常被终端操作一次进行合并和处理。

流的“懒”特性是为了优化。

代码语言:javascript
复制
List menu = new ArrayList<>();
        menu.add(new Dish("鱼香肉丝",500));
        menu.add(new Dish("鱼香茄子",800));
        menu.add(new Dish("红烧茄子",1000));

List names = menu.stream()
                .filter(dish -> {
                    System.out.println("filtering"+ dish.getName());
                    return dish.getCalories()>100;
                })
                .map(dish -> {
                    System.out.println("mapping" + dish.getName());
                    return dish.getName();
                })
                .limit(2)
                .collect(Collectors.toList());

输出结果:

可以看出

1 通过limit 只获取固定个数,不会整个遍历

2 filter和map 虽然是两个操作但是在同一个遍历中(循环合并)

我们看一下Stream的filter方法源码:

代码语言:javascript
复制
/**
     * Returns a stream consisting of the elements of this stream that match
     * the given predicate.
     *
     * This is an intermediate
     * operation.
     *
     * @param predicate a non-interfering,
     *                  stateless
     *                  predicate to apply to each element to determine if it
     *                  should be included
     * @return the new stream
     */
    Stream filter(Predicate predicate);

可以发现 中间操作的返回值都是Stream,而且根据注释可以清晰知道返回的是一个新的stream。

终端操作:

终端操作是为了产生结果,该结果是非stream的值,可以是List、Integer甚至也可以是void。

我们查看Stream的allMatch方法,发现返回值是boolean.

代码语言:javascript
复制
 /**
     * Returns whether all elements of this stream match the provided predicate.
     * May not evaluate the predicate on all elements if not necessary for
     * determining the result.  If the stream is empty then {@code true} is
     * returned and the predicate is not evaluated.
     *
     * This is a short-circuiting
     * terminal operation.
     *
     * @apiNote
     * This method evaluates the universal quantification of the
     * predicate over the elements of the stream (for all x P(x)).  If the
     * stream is empty, the quantification is said to be vacuously
     * satisfied and is always {@code true} (regardless of P(x)).
     *
     * @param predicate a non-interfering,
     *                  stateless
     *                  predicate to apply to elements of this stream
     * @return {@code true} if either all elements of the stream match the
     * provided predicate or the stream is empty, otherwise {@code false}
     */
    boolean allMatch(Predicate predicate);

核心思想类似 建造者模式,在建造者模式中,有一系列的调用来构建配置(在stream中称之为中间操作),然后调用build方法(在stream中就是终端操作)。

一个简单的例子;

代码语言:javascript
复制
        List menu = new ArrayList<>();
        menu.add(new Dish("鱼香肉丝",500));
        menu.add(new Dish("鱼香茄子",800));
        menu.add(new Dish("红烧茄子",1000));
        menu.add(new Dish("红烧鲍鱼",2000));

        List dishes = menu.stream()
                .filter(dish -> dish.getCalories()>300)
                .limit(3)
                .collect(Collectors.toList());

        System.out.println(dishes);

图解:

中间操作就像是管道一样,数据从前面“流到”经过中间操作一步一步流到后面,最终通过终端操作获取结果并关闭流。

总结

1、一个stream就是从一个资源构建的的支持数据处理操作一系列元素。

2、Stream 可以使用内部迭代,迭代独立于filter/map/sorted等操作。

3、有两种类型stream操作:中间操作和终端操作。

4、中间操作如filter和map返回一个stream允许进行链式编程。中间操作用来构建操作的管道但不产生任何结果。

5、终端操作如forEach、count和collect返回一个非stream值或执行stream管道并返回一个值。

6、stream中的元素是按需计算的。

2 、Stream“复用”

有的文章说“Stream执行终端操作后就被消费掉了,无法复用(这样说没错)”,给出一些曲折而且并非复用的方式,本质上还是重新创建Stream,如

https://blog.csdn.net/yiifaa/article/details/78118342

其实实现“复用”(对某个集合多次执行stream操作,请注意这并不是真正的复用),

但是我们要搞清楚自己的目的是啥,如果你的目的是对 集合多次使用stream执行终端操作,

最简单的做法就是将多次调用集合的.stream方法即可

代码语言:javascript
复制
List lists = new ArrayList<>();
        lists.add(new User("张三",22));
        lists.add(new User("张三",21));
        lists.add(new User("李四",22));
        lists.add(new User("张三",21));

        List collect = lists.stream().filter(user -> user.getAge() < 22).collect(Collectors.toList());

        List collect1 = lists.stream().filter(user -> user.getAge() > 50).collect(Collectors.toList());

我们看看stream方法的源码

代码语言:javascript
复制
    /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or late-binding. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a sequential {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a sequential {@code Stream} over the elements in this collection
     * @since 1.8
     */
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }

再进入 StreamSupport.stream方法

代码语言:javascript
复制
  /**
     * Creates a new sequential or parallel {@code Stream} from a
     * {@code Spliterator}.
     *
     * The spliterator is only traversed, split, or queried for estimated
     * size after the terminal operation of the stream pipeline commences.
     *
     * It is strongly recommended the spliterator report a characteristic of
     * {@code IMMUTABLE} or {@code CONCURRENT}, or be
     * late-binding.  Otherwise,
     * {@link #stream(java.util.function.Supplier, int, boolean)} should be used
     * to reduce the scope of potential interference with the source.  See
     * Non-Interference for
     * more details.
     *
     * @param  the type of stream elements
     * @param spliterator a {@code Spliterator} describing the stream elements
     * @param parallel if {@code true} then the returned stream is a parallel
     *        stream; if {@code false} the returned stream is a sequential
     *        stream.
     * @return a new sequential or parallel {@code Stream}
     */
    public static  Stream stream(Spliterator spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

通过代码和注释我们可以清楚地发现,返回值是一个新的stream,因此可以实现Stream的“复用”(其实是再次创建一个新的 stream)。

其他更多详细内容参考:

1、《Java 8 in Action: Lambdas, streams, and functional-style programming

2、http://www.importnew.com/17313.html

3、http://www.baeldung.com/java-groupingby-collector

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/07/18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、 基本介绍
  • 2 、Stream“复用”
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档