1. 程式人生 > >Java 8 Stream簡介和複用問題

Java 8 Stream簡介和複用問題

最近工作後開始使用Stream,用起來比較順手,可以說已經“沉浸於Stream無法自拔”,很少再用foreach迴圈了。

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

但是糾結於“Stream複用”問題。

這裡簡單整理一下。

參考資料  :《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,如

其實實現“複用”(對某個集合多次執行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