java8 streams_当Java 8 Streams API不够用时
java8 streams
Java 8與往常一樣是妥協和向后兼容的版本。 JSR-335專家組可能尚未與某些讀者就某些功能的范圍或可行性達成一致的版本 。 請參閱Brian Goetz關于為什么…的一些具體解釋。
- …Java 8默認方法中不允許“最終”
- …Java 8默認方法中不允許“同步”
但是今天,我們將專注于Streams API的“缺點”,或者正如Brian Goetz可能指出的那樣:考慮到設計目標,事情超出了范圍。
并行流?
并行計算很困難,曾經很痛苦。 當Java 7首次附帶新的(現在已舊的) Fork / Join API時,人們并不完全喜歡它。相反,顯然,調用Stream.parallel()的簡潔性是無與倫比的。
但是許多人實際上并不需要并行計算(不要與多線程混淆!)。 在95%的情況下,人們可能更喜歡功能更強大的Streams API,或者更通用的功能更強大的Collections API, Iterable各種Iterable子類型都有很多很棒的方法。
但是,更改Iterable是危險的。 甚至不Iterable通過潛在的Iterable.stream()方法將Iterable轉換為Stream 似乎也冒著打開潘多拉盒子的風險! 。
順序流!
因此,如果JDK不發貨,我們將自己創建!
流本身就很棒。 它們可能是無限的,這是一個很酷的功能。 通常,尤其是在函數式編程中,集合的大小并不重要,因為我們使用函數逐個元素地進行轉換。
如果我們允許Streams純粹是順序的,那么我們也可以使用以下任何一種很酷的方法(并行Streams也可以使用其中的一些方法):
- cycle() –使每個流無限的保證方式
- duplicate() –將一個流復制為兩個等效的流
- foldLeft() -順序和非締合的替代reduce()
- foldRight() -順序和非締合的替代reduce()
- limitUntil() –將流限制為在第一個記錄之前滿足謂詞的記錄
- limitWhile() –將流限制為第一個不滿足謂詞之前的記錄
- maxBy() –將流減少到最大映射值
- minBy() –將流減少到最小映射值
- partition() –將一個流劃分為兩個流,一個滿足一個謂詞,另一個不滿足相同的謂詞
- reverse() –以相反的順序產生一個新的流
- skipUntil() –跳過記錄直到滿足謂詞
- skipWhile() –只要滿足謂詞,就跳過記錄
- slice() –截取流的一部分,即合并skip()和limit()
- splitAt() –在給定位置將流分成兩個流
- unzip() –將成對的流分成兩個流
- zip() –將兩個流合并為一個成對的流
- zipWithIndex() –將流及其對應的索引流合并為單個對流
jOOλ的新Seq類型可以完成所有操作
以上所有都是jOOλ的一部分。 jOOλ(讀作“ jewel”或“ dju-lambda”,也用URL編寫jOOL等)是ASL 2.0許可的庫,它是在我們使用Java 8實現jOOQ集成測試時從我們自己的開發需求中產生的 。適用于編寫基于集合,元組,記錄和所有SQL原因的測試。
但是Streams API感覺有點不足,因此我們將JDK的Streams包裝到我們自己的Seq類型(用于序列/順序Stream的Seq)中:
我們已經將Seq擴展JDK Stream接口的新接口,因此您可以與其他Java API完全互操作地使用Seq保持現有方法不變:
public interface Seq<T> extends Stream<T> {/*** The underlying {@link Stream} implementation.*/Stream<T> stream();// [...] }現在,如果沒有元組,則函數式編程僅是樂趣的一半。 不幸的是,Java沒有內置的元組,雖然使用泛型創建元組庫很容易,但是當將Java與Scala或C#甚至VB.NET 進行比較時 ,元組仍然是第二類語法公民。
但是…
jOOλ也有元組
我們已經運行了一個代碼生成器來生成1-8度的元組(將來可能會添加更多,例如,以匹配Scala和jOOQ的 “魔術”度22)。
并且如果一個庫具有這樣的元組,則該庫也需要相應的功能。 這些TupleN和FunctionN類型的本質總結如下:
public class Tuple3<T1, T2, T3> implements Tuple, Comparable<Tuple3<T1, T2, T3>>, Serializable, Cloneable {public final T1 v1;public final T2 v2;public final T3 v3;// [...] }和
@FunctionalInterface public interface Function3<T1, T2, T3, R> {default R apply(Tuple3<T1, T2, T3> args) {return apply(args.v1, args.v2, args.v3);}R apply(T1 v1, T2 v2, T3 v3); }元組類型還有許多其他功能,但讓我們今天不討論它們。
附帶說明一下,我最近在reddit上與Gavin King(Hibernate的創建者)進行了有趣的討論 。 從ORM的角度來看,Java類似乎是SQL /關系元組的合適實現,而且確實如此。 從ORM角度來看。
但是類和元組在本質上是不同的,這對于大多數ORM來說都是一個非常微妙的問題- 例如,如Vlad Mihalcea所解釋的 。
此外,SQL的行值表達式(即元組)的概念與Java類可以建模的完全不同。 此主題將在后續的博客文章中介紹。
一些jOOλ示例
考慮到上述目標,讓我們來看一下如何通過示例來實現上述API:
拉鏈
// (tuple(1, "a"), tuple(2, "b"), tuple(3, "c")) Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c"));// ("1:a", "2:b", "3:c") Seq.of(1, 2, 3).zip(Seq.of("a", "b", "c"), (x, y) -> x + ":" + y );// (tuple("a", 0), tuple("b", 1), tuple("c", 2)) Seq.of("a", "b", "c").zipWithIndex();// tuple((1, 2, 3), (a, b, c)) Seq.unzip(Seq.of(tuple(1, "a"),tuple(2, "b"),tuple(3, "c") ));元組已經變得非常方便了。 當我們將兩個流“壓縮”到一個流中時,我們需要一個將兩個值組合在一起的包裝器值類型。 傳統上,人們可能會使用Object[]進行快速處理,但是數組不會指示屬性類型或程度。
不幸的是,Java編譯器無法Seq<T> <T>類型的有效界限。 這就是為什么我們只能有一個靜態unzip()方法(而不是實例方法)的原因,該方法的簽名如下所示:
// This works static <T1, T2> Tuple2<Seq<T1>, Seq<T2>> unzip(Stream<Tuple2<T1, T2>> stream) { ... }// This doesn't work: interface Seq<T> extends Stream<T> {Tuple2<Seq<???>, Seq<???>> unzip(); }跳過和限制
// (3, 4, 5) Seq.of(1, 2, 3, 4, 5).skipWhile(i -> i < 3);// (3, 4, 5) Seq.of(1, 2, 3, 4, 5).skipUntil(i -> i == 3);// (1, 2) Seq.of(1, 2, 3, 4, 5).limitWhile(i -> i < 3);// (1, 2) Seq.of(1, 2, 3, 4, 5).limitUntil(i -> i == 3);其他功能庫可能使用的術語與跳過(例如,丟棄)和限制(例如,采用)不同。 最終這并不重要。 我們選擇了現有Stream API中已經存在的術語: Stream.skip()和Stream.limit()
折疊式
// "abc" Seq.of("a", "b", "c").foldLeft("", (u, t) -> t + u);// "cba" Seq.of("a", "b", "c").foldRight("", (t, u) -> t + u);Stream.reduce()操作專為并行化而設計。 這意味著傳遞給它的函數必須具有以下重要屬性:
- 關聯性
- 不干涉
- 無國籍
但是有時候,您真的想用不具有上述屬性的函數來“減少”流,因此,您可能并不在乎這種減少是否可以并行化。 這是“折疊”出現的地方。
在此處可以看到有關縮小和折疊(在Scala中)的各種差異的很好的解釋。
分裂
// tuple((1, 2, 3), (1, 2, 3)) Seq.of(1, 2, 3).duplicate();// tuple((1, 3, 5), (2, 4, 6)) Seq.of(1, 2, 3, 4, 5, 6).partition(i -> i % 2 != 0)// tuple((1, 2), (3, 4, 5)) Seq.of(1, 2, 3, 4, 5).splitAt(2);上面的功能都有一個共同點:它們在單個流上運行以產生兩個新的流,這些流可以獨立使用。
顯然,這意味著在內部必須消耗一些內存以保留部分消耗的流的緩沖區。 例如
- 復制需要跟蹤一個流中已消耗的所有值,而另一流中沒有
- 分區需要快速前進到滿足(或不滿足)謂詞的下一個值,而不會丟失所有丟棄的值
- 拆分可能需要快速前進到拆分索引
為了獲得一些真正的功能樂趣,讓我們看一下可能的splitAt()實現:
static <T> Tuple2<Seq<T>, Seq<T>> splitAt(Stream<T> stream, long position) {return seq(stream).zipWithIndex().partition(t -> t.v2 < position).map((v1, v2) -> tuple(v1.map(t -> t.v1),v2.map(t -> t.v1))); }…或附有評論:
static <T> Tuple2<Seq<T>, Seq<T>> splitAt(Stream<T> stream, long position) {// Add jOOλ functionality to the stream// -> local Type: Seq<T>return seq(stream)// Keep track of stream positions// with each element in the stream// -> local Type: Seq<Tuple2<T, Long>>.zipWithIndex()// Split the streams at position// -> local Type: Tuple2<Seq<Tuple2<T, Long>>,// Seq<Tuple2<T, Long>>>.partition(t -> t.v2 < position)// Remove the indexes from zipWithIndex again// -> local Type: Tuple2<Seq<T>, Seq<T>>.map((v1, v2) -> tuple(v1.map(t -> t.v1),v2.map(t -> t.v1))); }很好,不是嗎? 另一方面, partition()可能實現要復雜一些。 這里Spliterator地用Iterator而不是新的Spliterator :
static <T> Tuple2<Seq<T>, Seq<T>> partition(Stream<T> stream, Predicate<? super T> predicate ) {final Iterator<T> it = stream.iterator();final LinkedList<T> buffer1 = new LinkedList<>();final LinkedList<T> buffer2 = new LinkedList<>();class Partition implements Iterator<T> {final boolean b;Partition(boolean b) {this.b = b;}void fetch() {while (buffer(b).isEmpty() && it.hasNext()) {T next = it.next();buffer(predicate.test(next)).offer(next);}}LinkedList<T> buffer(boolean test) {return test ? buffer1 : buffer2;}@Overridepublic boolean hasNext() {fetch();return !buffer(b).isEmpty();}@Overridepublic T next() {return buffer(b).poll();}}return tuple(seq(new Partition(true)), seq(new Partition(false))); }我將讓您進行練習并驗證上面的代碼。
立即獲取并為jOOλ做出貢獻!
以上所有內容都是jOOλ的一部分,可從GitHub免費獲得。 已經有部分的Java-8就緒,全面的庫調用functionaljava ,它走的更遠,比jOOλ。
但是,我們相信Java 8的Streams API所缺少的實際上只是對順序流非常有用的幾種方法。
在上一篇文章中,我們展示了如何使用簡單的JDBC包裝器將lambda引入基于StringSQL中 ( 當然,我們仍然認為應該使用jOOQ代替 )。
今天,我們已經展示了如何使用jOOλ輕松編寫出色的功能和順序流處理。
請繼續關注,以在不久的將來獲得更多的好處(當然,非常歡迎拉動請求!)
翻譯自: https://www.javacodegeeks.com/2014/09/when-the-java-8-streams-api-is-not-enough.html
java8 streams
總結
以上是生活随笔為你收集整理的java8 streams_当Java 8 Streams API不够用时的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果13震动快捷键(苹果13震动怎么调出
- 下一篇: 联想笔记本电脑(联想笔记本电脑哪款好用性