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》