stream and lambda(7) - stream 的建立
Stream 的建立, Stream
介面本身提供了一些方法來完成這個操作。而除此之外,JDK 本身也提供了其他類來完成這個操作。考慮到 Stream 的集合息息相關,合理猜測 Arrays
, Collection
, Map
可能會提供建立 stream 的方法。但 Stream<T>
只有單泛型,而 Map<K,V>
是鍵值對,目測 Map
中不含有 Stream
相關的方法。來,大家可以看原始碼驗證一下。
通過檢視 JDK 原始碼發現, Arrays
、 Collection
均提供 stream 相關的方法,而 Map
並未提供對應的方法。
Arrays
從方法來看,Arrays 提供了多個過載方法,可以將 int
、 long
、 double
以及例項物件陣列轉為 Stream
,並且提供了擷取陣列區間的功能。如以下用例,就是將陣列轉成 stream。
public void arraysCreateTest() { String[] strings = {"I", "am", "arrays", "demo"}; int[] nums = {1, 0, 0, 8, 6}; Arrays.stream(strings).forEach(System.out::println); Arrays.stream(nums, 2, 5).forEach(System.out::println); }
Collection
Collection 提供的 stream()
和 parallelStream()
方法已經在介面內實現,其子類,如 List
和 Set
可以直接使用。
public void collectionCreateTest() { List<Integer> integers = Arrays.asList(1, 2, 9, 11, 5, 7, 3); integers.stream().sorted().forEach(System.out::println); integers.parallelStream().sorted().forEachOrdered(System.out::println); }
Stream
empty
empty()
方法,返回的是一個空的 stream。既然沒有任何元素,那這個 stream 的意義又在哪裡?
不報 NPE 異常,還不夠你臭屁的?要知道, java.util.Collections
還單獨提供了 emptySet()
和 emptyList()
方法呢。
來試一試,下面的程式碼能不能正常執行。
public void emptyTest(){ Stream<Integer> empty = Stream.empty(); System.out.println(empty.findFirst().orElse(-1)); empty = Stream.empty(); empty.forEach(System.out::println); }
generate
generate
是個可以產生無盡流的方法。首先,我們先看看下面的程式碼的執行結果。
public void endlessGenerateTest() { Stream<Integer> generate = Stream.generate(() -> new SecureRandom().nextInt()); AtomicLong al = new AtomicLong(0); generate.forEach(integer -> System.out.println(al.incrementAndGet() + " : " + integer)); }
怎麼樣,讓我聽到你們電腦的歡呼聲(此處應有滑稽表情)。好了,效果大家也看到了,我們來看下方法的定義:
static <T> Stream<T> generate(Supplier<T> s)
通過傳入一個 Supplier
,來生成一個 stream。生成內部元素的時候,每生成一個,就會向 Supplier
再要一個,如此下來產生的就是無盡流。
不過,也不用擔心使用該方法後程序就停不下來,或者是 Supplier
產生了過量的元素導致效能低下。
public void limitGenerateTest() { Stream<Integer> generate = Stream.generate(() -> { System.out.println("#######"); return new SecureRandom().nextInt(); }); AtomicLong al = new AtomicLong(0); generate.limit(3).forEach(integer -> System.out.println(al.incrementAndGet() + " : " + integer)); }
輸出結果如下:
####### 1 : -35704083 ####### 2 : -1875554696 ####### 3 : 541096732
可以看出來,使用 limit(3)
的時候,是每次需要時才產生新元素。
of
先看方法定義:
static <T> Stream<T> of(T t) static <T> Stream<T> of(T… values)
好的食材,往往通過最簡單的烹飪即可;而好用的方法,往往是一看就知道,一上手就會。 of
方法,傳入可變個數的引數,均可以轉為 stream。
public void ofTest() { Stream<String> strStream = Stream.of("this is stream.of demo"); strStream.forEach(System.out::println); Stream<Integer> intStream = Stream.of(1, 0, 0, 8, 6); intStream.forEach(System.out::println); List<String> list = Arrays.asList("i", "am", "list", "of", "method"); Stream<List<String>> listStream = Stream.of(list); listStream.forEach(System.out::println); }
注意:如果傳入的引數是一個集合,如 List 或者 Set,流內的元素也是隻有一個,而不是集合內的元素。流內的元素,就是那個集合。
iterate
iterate
聽起來是不是有那麼點熟悉?再想想,集合的迭代是怎麼實現的?如果還沒想到,再提示下,關鍵字 hasNext
。雖然集合有不少不用 Iterator
的遍歷寫法,但是這個迭代器還是要了解的。來先看下 iterate
方法定義:
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
其中, T seed
是第一個元素,後續元素通過迭代計算出來。這樣滾動迭代,就產生了無限流。這點和 generate
其實有些相似,只不過 generate
的前後元素沒有關聯。
Stream.iterate(1, iter -> iter + 3).limit(5).forEach(System.out::print);
比如這個例子,將依次輸出 1,4,7,10,13
。
concat
嚴格來說, concat
並不算是建立型別的,畢竟建立型別是從無到有,是創造;而 concat
站在了前人的肩膀上,是創新,將已有的兩個流合併到一起。
照例,我們先看方法定義:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
concat,將已有的兩個流合併到一起。文件更明確的說了以下的點:
- 更準確的說,是將第二個流放到第一個流屁股後面。
- 如果兩個流都是 ordered,那麼得到的新流也是 ordered。
- 如果兩個流有一個是並行流,則得到的新流是並行流。
- 關閉合並結果流時,會呼叫兩個輸入流的關閉方法。
當然,看原始碼的實現,上面的這些點是可以看出來的。拋開原始碼,我們來驗證一下。
合成新流的元素順序
public void concatTest1() { Stream<Integer> first = Stream.of(1, 3, 5); Stream<Integer> second = Stream.of(2, 3, 4); System.out.println(Stream.concat(first, second).collect(Collectors.toList())); }
經驗證,輸出的內容是:
[1, 3, 5, 2, 3, 4]
就這驗證了,合成的流,順序確實是第一個流 + 第二個流, 並且不會去重 。
什麼是 ordered
流是 ordered, 並不是指對流的元素進行排序 。而是指流內的元素,是有固定的順序約束的。比如 List
,元素是有約束順序的: ArrayList
有陣列座標, LinkedList
有連結串列連線。所以, List
生成的流是 ordered。
更詳細的內容,下次單獨再說吧。
合併後的並行流
public void concatTest2() { Stream<Integer> first = Stream.of(1, 3, 5).parallel(); Stream<Integer> second = Stream.of(2, 3, 4); Stream.concat(first, second).map(i -> i + " ").forEach(System.out::print); }
這次的測試,把第一個流換成了並行流。如果合併後的流不是並行,那麼輸出的內容應該是原定的順序 1 3 5 2 3 4
。
驗證後的結果卻是輸出是無序的,說明了合併後的流是並行流:
合併後流的關閉
public void concatTest3() { Stream<Integer> first = Stream.of(1, 3, 5); Stream<Integer> second = Stream.of(2, 3, 4); System.out.println(Stream.concat(first, second).collect(Collectors.toList())); Stream.concat(first, second).map(i -> i + " ").forEach(System.out::print); }
為了驗證這個問題,我們把流合併兩次看一下。在執行的時候,實際拋瞭如下異常:
java.lang.IllegalStateException: stream has already been operated upon or closed
其實,從原始碼來看,在合併成新流之後,first 和 second 兩個流都執行了 close 。
總結
Stream 的建立, Arrays
和 Collection
也是出了一把力的。打鐵還需自身硬, Stream
本身提供的這些建立方法,覆蓋面也是極廣的。畢竟出門靠朋友, Stream
就這麼和集合打成一片了。
注:本文配套程式碼可在 github
檢視: stream-and-lambda
- stream and lambda(18) - 終止操作之 stream 收集器 collectors
- stream and lambda(15) - 終止操作之 stream 陣列操作 toArray
- stream and lambda(13) - 終止操作之計數 count 與比較 min、max
- stream and lambda(12) - 終止操作之查詢與匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)
- stream and lambda(11) - Optional 簡介
- stream and lambda(10) - 中間操作之排序sorted與除錯peek
- stream and lambda(9) - 中間操作之map操作(map、flatmap)
- stream and lambda(8) - 中間操作之篩選操作(filter、distinct、limit、skip)
- stream and lambda(7) - stream 的建立
- stream and lambda(6) - stream 簡介
- stream and lambda(5) - lambda 表示式最佳實踐
- stream and lambda(3) - jdk 提供的函式式介面
- stream and lambda(2) - 函式式介面簡介
- stream and lambda(1) - lambda 簡介