Java 8 Stream简介和复用问题

最近工作后开始使用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)。

中间操作的特性

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

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

List<Dish> menu = new ArrayList<>();
        menu.add(new Dish("鱼香肉丝",500));
        menu.add(new Dish("鱼香茄子",800));
        menu.add(new Dish("红烧茄子",1000));

List<String> 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方法源码:

/**
     * Returns a stream consisting of the elements of this stream that match
     * the given predicate.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                  <a href="package-summary.html#Statelessness">stateless</a>
     *                  predicate to apply to each element to determine if it
     *                  should be included
     * @return the new stream
     */
    Stream<T> filter(Predicate<? super T> predicate);

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

终端操作:

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

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

 /**
     * 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.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">short-circuiting
     * terminal operation</a>.
     *
     * @apiNote
     * This method evaluates the <em>universal quantification</em> 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 <em>vacuously
     * satisfied</em> and is always {@code true} (regardless of P(x)).
     *
     * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                  <a href="package-summary.html#Statelessness">stateless</a>
     *                  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<? super T> predicate);

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

一个简单的例子;


        List<Dish> menu = new ArrayList<>();
        menu.add(new Dish("鱼香肉丝",500));
        menu.add(new Dish("鱼香茄子",800));
        menu.add(new Dish("红烧茄子",1000));
        menu.add(new Dish("红烧鲍鱼",2000));

        List<Dish> 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方法

List<User> lists = new ArrayList<>();
        lists.add(new User("张三",22));
        lists.add(new User("张三",21));
        lists.add(new User("李四",22));
        lists.add(new User("张三",21));

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

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

我们看看stream方法的源码

    /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (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<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

再进入 StreamSupport.stream方法

  /**
     * Creates a new sequential or parallel {@code Stream} from a
     * {@code Spliterator}.
     *
     * <p>The spliterator is only traversed, split, or queried for estimated
     * size after the terminal operation of the stream pipeline commences.
     *
     * <p>It is strongly recommended the spliterator report a characteristic of
     * {@code IMMUTABLE} or {@code CONCURRENT}, or be
     * <a href="../Spliterator.html#binding">late-binding</a>.  Otherwise,
     * {@link #stream(java.util.function.Supplier, int, boolean)} should be used
     * to reduce the scope of potential interference with the source.  See
     * <a href="package-summary.html#NonInterference">Non-Interference</a> for
     * more details.
     *
     * @param <T> 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 <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
        Objects.requireNonNull(spliterator);
        return new ReferencePipeline.Head<>(spliterator,
                                            StreamOpFlag.fromCharacteristics(spliterator),
                                            parallel);
    }

通过代码和注释我们可以清楚地发现,返回值是一个新的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

猜你喜欢

转载自blog.csdn.net/w605283073/article/details/81104844