Java Stream API入門篇

yuyaojin 7年前發布 | 16K 次閱讀 Java API Java開發

你可能沒意識到Java對函數式編程的重視程度,看看Java 8加入函數式編程擴充多少功能就清楚了。Java 8之所以費這么大功夫引入函數式編程,原因有二:

  1. 代碼簡潔 ,函數式編程寫出的代碼簡潔且意圖明確,使用 stream 接口讓你從此告別 for 循環。
  2. 多核友好 ,Java函數式編程使得編寫并行程序從未如此簡單,你需要的全部就是調用一下 parallel() 方法。

這一節我們學習 stream ,也就是Java函數式編程的主角。對于Java 7來說 stream 完全是個陌生東西, stream 并不是某種數據結構,它只是數據源的一種視圖。這里的數據源可以是一個數組,Java容器或I/O channel等。正因如此要得到一個 stream 通常不會手動創建,而是調用對應的工具方法,比如:

  • 調用 Collection.stream() 或者 Collection.parallelStream() 方法
  • 調用 Arrays.stream(T[] array) 方法

常見的 stream 接口繼承關系如圖:

圖中4種 stream 接口繼承自 BaseStream ,其中 IntStream, LongStream, DoubleStream 對應三種基本類型( int, long, double ,注意不是包裝類型), Stream 對應所有剩余類型的 stream 視圖。為不同數據類型設置不同 stream 接口,可以1.提高性能,2.增加特定接口函數。

你可能會奇怪為什么不把 IntStream 等設計成 Stream 的子接口?畢竟這接口中的方法名大部分是一樣的。答案是這些方法的名字雖然相同,但是返回類型不同,如果設計成父子接口關系,這些方法將不能共存,因為Java不允許只有返回類型不同的方法重載。

雖然大部分情況下 stream 是容器調用 Collection.stream() 方法得到的,但 streamcollections 有以下不同:

  • 無存儲stream 不是一種數據結構,它只是某種數據源的一個視圖,數據源可以是一個數組,Java容器或I/O channel等。
  • 為函數式編程而生 。對 stream 的任何修改都不會修改背后的數據源,比如對 stream 執行過濾操作并不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新 stream
  • 惰式執行stream 上的操作并不會立即執行,只有等到用戶真正需要結果的時候才會執行。
  • 可消費性stream 只能被“消費”一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須重新生成。

stream 的操作分為為兩類, 中間操作( intermediate operations )和結束操作( terminal operations ) ,二者特點是:

  1. 中間操作總是會惰式執行 ,調用中間操作只會生成一個標記了該操作的新 stream ,僅此而已。
  2. 結束操作會觸發實際計算 ,計算發生時會把所有中間操作積攢的操作以 pipeline 的方式執行,這樣可以減少迭代次數。計算完成之后 stream 就會失效。

如果你熟悉Apache Spark RDD,對 stream 的這個特點應該不陌生。

下表匯總了 Stream 接口的部分常見方法:

操作類型 接口方法
中間操作 concat() distinct() filter() flatMap() limit() map() peek()
skip() sorted() parallel() sequential() unordered()
結束操作 allMatch() anyMatch() collect() count() findAny() findFirst()
forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

區分中間操作和結束操作最簡單的方法,就是看方法的返回值,返回值為 stream 的大都是中間操作,否則是結束操作。

stream方法使用

stream 跟函數接口關系非常緊密,沒有函數接口 stream 就無法工作。回顧一下: 函數接口是指內部只有一個抽象方法的接口 。通常函數接口出現的地方都可以使用Lambda表達式,所以不必記憶函數接口的名字。

forEach()

我們對 forEach() 方法并不陌生,在 Collection 中我們已經見過。方法簽名為 void forEach(Consumer<? super E> action) ,作用是對容器中的每個元素執行 action 指定的動作,也就是對元素進行遍歷。

// 使用Stream.forEach()迭代
Stream<String> stream = Stream.of("I", "love", "you", "too");
stream.forEach(str -> System.out.println(str));

由于 forEach() 是結束方法,上述代碼會立即執行,輸出所有字符串。

filter()

函數原型為 Stream<T> filter(Predicate<? super T> predicate) ,作用是返回一個只包含滿足 predicate 條件元素的 Stream 。

// 保留長度等于3的字符串
Stream<String> stream= Stream.of("I", "love", "you", "too");
stream.filter(str -> str.length()==3)
    .forEach(str -> System.out.println(str));

上述代碼將輸出為長度等于3的字符串 you 和 too 。注意,由于 filter() 是個中間操作,如果只調用 filter() 不會有實際計算,因此也不會輸出任何信息。

distinct()

函數原型為 Stream<T> distinct() ,作用是返回一個去除重復元素之后的 Stream 。

Stream<String> stream= Stream.of("I", "love", "you", "too", "too");
stream.distinct()
    .forEach(str -> System.out.println(str));

上述代碼會輸出去掉一個 too 之后的其余字符串。

sorted()

排序函數有兩個,一個是用自然順序排序,一個是使用自定義比較器排序,函數原型分別為 Stream<T> sorted() 和 Stream<T> sorted(Comparator<? super T> comparator) 。

Stream<String> stream= Stream.of("I", "love", "you", "too");
stream.sorted((str1, str2) -> str1.length()-str2.length())
    .forEach(str -> System.out.println(str));

上述代碼將輸出按照長度升序排序后的字符串,結果完全在預料之中。

map()

函數原型為 <R> Stream<R> map(Function<? super T,? extends R> mapper) ,作用是返回一個對當前所有元素執行執行 mapper 之后的結果組成的 Stream 。直觀的說,就是對每個元素按照某種操作進行轉換,轉換前后 Stream 中元素的個數不會改變,但元素的類型取決于轉換之后的類型。

Stream<String> stream    = Stream.of("I", "love", "you", "too");
stream.map(str -> str.toUpperCase())
    .forEach(str -> System.out.println(str));

上述代碼將輸出原字符串的大寫形式。

flatMap()

函數原型為 <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) ,作用是對每個元素執行 mapper 指定的操作,并用所有 mapper 返回的 Stream 中的元素組成一個新的 Stream 作為最終返回結果。說起來太拗口,通俗的講 flatMap() 的作用就相當于把原 stream 中的所有元素都"攤平"之后組成的 Stream ,轉換前后元素的個數和類型都可能會改變。

Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
stream.flatMap(list -> list.stream())
    .forEach(i -> System.out.println(i));

上述代碼中,原來的 stream 中有兩個元素,分別是兩個 List<Integer> ,執行 flatMap() 之后,將每個 List 都“攤平”成了一個個的數字,所以會新產生一個由5個數字組成的 Stream 。所以最終將輸出1~5這5個數字。

結語

截止到目前我們感覺良好,已介紹 Stream API理解起來并不費勁兒。如果你就此以為函數式編程不過如此,恐怕是高興地太早了。

 

 

來自:http://www.cnblogs.com/CarpenterLee/p/6545321.html

 

 本文由用戶 yuyaojin 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!