Java 8的惰性序列实现
我剛剛在GitHub上發(fā)布了LazySeq庫,這是我最近進行的Java 8實驗的結(jié)果。 我希望你會喜歡它。 即使您覺得它不是很有用,它仍然是Java 8(以及一般而言)中的函數(shù)式編程的重要課程。 而且它可能是第一個針對Java 8的社區(qū)庫!
介紹
惰性序列是僅在實際需要其元素時才計算的數(shù)據(jù)結(jié)構(gòu)。 對延遲序列的所有操作map()例如map()和filter()也都是延遲的,從而將調(diào)用延遲到真正必要的時候。 總是從一開始就使用非常便宜的first / rest遍歷惰性序列
分解( head()和tail() )。 惰性序列的一個重要屬性是它們可以表示無限的數(shù)據(jù)流,例如,隨時間推移的所有自然數(shù)或溫度測量值。
惰性序列會記住已計算的值,因此,如果您訪問第N個元素,則也會計算并緩存從1到N-1所有元素。 盡管如此, LazySeq (處于許多功能語言和算法的核心)是不可變的且線程安全的。
基本原理
該庫在很大程度上受到scala.collection.immutable.Stream啟發(fā),旨在提供不可變,線程安全且易于使用的惰性序列實現(xiàn)(可能是無限的)。 有關(guān)某些用例,請參見Scala和Clojure中的惰性序列 。
Stream類名稱是用Java 8已被使用,因此LazySeq被選擇,類似于lazy-seq Clojure中 。 說到Stream ,一開始它看起來像是一個開箱即用的惰性序列實現(xiàn)。 但是,引用Javadoc:
流不是數(shù)據(jù)結(jié)構(gòu)
和:
一旦在流上執(zhí)行了某個操作,該操作將被視為已消耗并且不再可用于其他操作。
換句話說, java.util.stream.Stream只是現(xiàn)有集合的一個瘦包裝,適合一次使用。 更類似于Iterator ,而不是Stream于斯卡拉。 該庫試圖填補這一空白。
當然,在Java 8之前可以實現(xiàn)惰性序列數(shù)據(jù)結(jié)構(gòu),但是缺少lambda使得使用這種數(shù)據(jù)結(jié)構(gòu)變得乏味且過于冗長。
入門
在10分鐘內(nèi)構(gòu)建并處理惰性序列。
所有自然數(shù)的無限序列
為了創(chuàng)建一個惰性序列,您可以使用LazySeq.cons()工廠方法,該方法接受第一個元素( head )和一個以后可能用于計算rest( tail )的函數(shù)。 例如,為了產(chǎn)生具有給定起始元素的自然數(shù)的惰性序列,您只需說:
private LazySeq<Integer> naturals(int from) {return LazySeq.cons(from, () -> naturals(from + 1)); }這里真的沒有遞歸。 如果存在,則調(diào)用naturals()會很快導致StackOverflowError因為它會在沒有停止條件的情況下自行調(diào)用。 但是() -> naturals(from + 1)表達式定義了一個函數(shù),該函數(shù)返回此數(shù)據(jù)結(jié)構(gòu)將調(diào)用的LazySeq<Integer> (準確地說是Supplier ),但僅在需要時才返回。 查看下面的代碼,您認為幾次調(diào)用了naturals()函數(shù)(第一行除外)?
final LazySeq<Integer> ints = naturals(2);final LazySeq<String> strings = ints.map(n -> n + 10).filter(n -> n % 2 == 0).take(10).flatMap(n -> Arrays.asList(0x10000 + n, n)).distinct().map(Integer::toHexString);的第一次調(diào)用naturals(2)返回來自起始懶惰序列2但休息( 3 , 4 , 5 ,...)還沒有被計算。 稍后,我們將map()到該序列上,對其進行filter() , take()前10個元素,刪除重復項,等等。所有這些操作都不會評估序列,并且盡可能懶惰。 例如take(10)不會急切地求出前10個元素以返回它們。 而是返回新的惰性序列,該序列記住它應(yīng)該在第10個元素處截斷原始序列。
同樣適用于distinct() 。 它不會評估提取所有唯一值的整個序列(否則,上面的代碼將Swift爆炸,遍歷無限量的自然數(shù))。 而是返回僅包含第一個元素的新序列。 如果您要第二個唯一元素,它將懶惰地評估尾巴,但只會盡可能多。 查看
toString()輸出:
問號( ? )說: “該集合中可能還有其他內(nèi)容,但我還不知道” 。 您了解1000c來自何處嗎? 仔細地看:
如您所見,這些操作都不是真正需要評估整個流的。 唯一的頭正在轉(zhuǎn)變,這就是我們最終看到的。 那么,何時實際評估此數(shù)據(jù)結(jié)構(gòu)? 在絕對必要時(例如在副作用遍歷期間):
strings.force();//or strings.forEach(System.out::println);//or final List<String> list = strings.toList();//or for (String s : strings) {System.out.println(s); } 僅上述所有語句將強制評估整個延遲序列。 如果我們的序列是無限的,不是很聰明,但是
strings僅限于前10個元素,因此不會無限運行。 如果只想強制執(zhí)行序列的一部分,只需調(diào)用strings.take(5).force() 。 順便說一句,您是否注意到我們可以使用標準Java 5 for-each語法遍歷LazySeq strings ? 這是因為LazySeq實現(xiàn)了List接口,因此可以與Java Collections Framework生態(tài)系統(tǒng)很好地配合使用:
請記住,一旦對惰性序列進行了評估(計算),它將對它們進行緩存( 記住 )以備后用。 這使得惰性序列非常適合表示無限或非常長的數(shù)據(jù)流,這些數(shù)據(jù)計算起來很昂貴。
迭代()
建立無限的惰性序列通常可以歸結(jié)為提供一個初始元素和一個功能,該功能可以根據(jù)前一個元素生成下一個元素。 換句話說,第二元素是第一個的函數(shù),第三元素是第二個的函數(shù),依此類推。 為這種情況提供了便利的LazySeq.iterate()函數(shù)。 現(xiàn)在, ints定義可以如下所示:
final LazySeq<Integer> ints = LazySeq.iterate(2, n -> n + 1);我們從2開始,每個后續(xù)元素表示為前一個元素+ 1。
更多示例:斐波那契數(shù)列和Collat??z猜想
沒有斐波那契數(shù)字,就不會留下關(guān)于懶惰數(shù)據(jù)結(jié)構(gòu)的文章,例如:
private static LazySeq<Integer> lastTwoFib(int first, int second) {return LazySeq.cons(first,() -> lastTwoFib(second, first + second)); }斐波那契數(shù)列也是無限的,但我們可以通過多種方式自由變換它:
System.out.println(fib.drop(5).take(10).toList() ); //[5, 8, 13, 21, 34, 55, 89, 144, 233, 377]final int firstAbove1000 = fib.filter(n -> (n > 1000)).head();fib.get(45);看到無限的數(shù)字流是多么容易和自然嗎? drop(5).take(10)跳過前5個元素,并顯示下一個10。在這一點上,已經(jīng)計算了前15個數(shù)字,以后再也不會計算了。
查找高于1000(可能是1597 )的第一個斐波那契數(shù)非常簡單。 head()始終由filter()預(yù)先計算,因此不需要進一步評估。 最后但并非最不重要的一點是,我們只需索取第45個斐波那契數(shù) (從0開始)并獲得
1134903170 。 如果您嘗試訪問該編號之前的任何斐波那契數(shù),它們將被預(yù)先計算并可以快速檢索。
有限序列(Collat??z猜想)
Collat??z猜想也是一個很有趣的問題。 對于每個正整數(shù)n我們使用以下算法計算下一個整數(shù):
- 如果n為偶數(shù)則為n n/2
- 如果n為奇數(shù),則為3n + 1
例如,從10序列開始的外觀如下:10、5、16、8、4、2、1。該序列在達到1時結(jié)束。數(shù)學家認為,從任何整數(shù)開始,我們最終都將達到1,但尚未證明。
讓我們創(chuàng)建一個惰性序列,該序列為給定的n生成Collat??z級數(shù),但僅根據(jù)需要生成。 如上所述,這次我們的序列將是有限的:
private LazySeq<Long> collatz(long from) {if (from > 1) {final long next = from % 2 == 0 ? from / 2 : from * 3 + 1;return LazySeq.cons(from, () -> collatz(next));} else {return LazySeq.of(1L);} }該實現(xiàn)由定義直接驅(qū)動。 對于每個大于1數(shù)字,返回該數(shù)字+惰性計算的流的其余部分( () -> collatz(next) )。 如您所見,如果給定1 ,我們將使用特殊的of()工廠方法返回單元素惰性序列。 讓我們用上述10測試它:
final LazySeq<Long> collatz = collatz(10);collatz.filter(n -> (n > 10)).head(); collatz.size();filter()允許我們找到大于10的序列中的第一個數(shù)字。 請記住,惰性序列將必須遍歷內(nèi)容(進行自我評估),但只能遍歷找到第一個匹配元素的位置。 然后停止,確保其計算量盡可能少。
但是,為了計算元素總數(shù), size()必須遍歷整個序列。 當然,這僅適用于有限的惰性序列,在無限序列上調(diào)用size()最終效果不佳。
如果您對此序列稍作練習,您將很快意識到,不同數(shù)字的序列共享相同的后綴 (總是以相同的數(shù)字序列結(jié)尾)。 這請求進行一些緩存/結(jié)構(gòu)共享。 有關(guān)詳細信息,請參見CollatzConjectureTest 。
現(xiàn)實生活?
無限的數(shù)字序列很棒,但在現(xiàn)實生活中并不十分實用。 也許還有更多腳踏實地的例子? 假設(shè)您有一個收藏,您需要從該收藏中隨機挑選一些物品。 代替集合,我將使用一個返回隨機拉丁字符的函數(shù):
private char randomChar() {return (char) ('A' + (int) (Math.random() * ('Z' - 'A' + 1))); }但是有一個轉(zhuǎn)折。 您需要N個(N <26,拉丁字符個數(shù))唯一值。 僅僅幾次調(diào)用randomChar()并不能保證唯一性。 解決這個問題的方法很少,使用LazySeq非常簡單:
LazySeq<Character> charStream = LazySeq.<Character>continually(this::randomChar); LazySeq<Character> uniqueCharStream = charStream.distinct();當需要時, continually()只需為每個元素調(diào)用給定函數(shù)。 因此, charStream將是無限的隨機字符流。 當然,它們不能唯一。 但是uniqueCharStream保證其輸出是唯一的。 它通過檢查底層charStream下一個元素并拒絕已出現(xiàn)的項目來實現(xiàn)。 現(xiàn)在我們可以說uniqueCharStream.take(4)并確保不會出現(xiàn)重復項。
再次注意, continually(this::randomChar).distinct().take(4)實際上只調(diào)用randomChar()一次! 只要您不消耗此序列,它就會保持延遲并盡可能延遲評估。
另一個示例涉及從數(shù)據(jù)庫加載數(shù)據(jù)的批次(頁面)。 使用ResultSet或Iterator很麻煩,但是將整個數(shù)據(jù)集加載到內(nèi)存中通常不可行。 另一種選擇是急于加載第一批數(shù)據(jù),然后提供加載下一批數(shù)據(jù)的功能。 僅當確實需要數(shù)據(jù)時才加載數(shù)據(jù),而我們不會遇到性能或可伸縮性問題。
首先,讓我們定義抽象的API,以從數(shù)據(jù)庫中加載批量數(shù)據(jù):
public List<Record> loadPage(int offset, int max) {//load records from offset to offset + max }我完全從技術(shù)中抽象出來,但是您明白了。 假設(shè)我們現(xiàn)在定義LazySeq<Record> ,它從第0行開始,僅在需要時才加載下一頁:
public static final int PAGE_SIZE = 5;private LazySeq<Record> records(int from) {return LazySeq.concat(loadPage(from, PAGE_SIZE),() -> records(from + PAGE_SIZE)); }通過調(diào)用records(0)創(chuàng)建新的LazySeq<Record>實例時,將加載5個元素的第一頁。 這意味著已經(jīng)計算出前5個序列元素。 如果您嘗試訪問6th或更高版本,sequence將自動加載所有丟失的記錄并對其進行緩存。 換句話說,您永遠不會兩次計算相同的元素。
使用序列時,更有用的工具是grouped()和sliding()方法。 首先將輸入序列分成大小相等的組。 以這個為例,還證明這些方法總是很懶:
final LazySeq<Character> chars = LazySeq.of('A', 'B', 'C', 'D', 'E', 'F', 'G');chars.grouped(3); //[[A, B, C], ?]chars.grouped(3).force(); //force evaluation //[[A, B, C], [D, E, F], [G]]同樣適用于sliding() :
chars.sliding(3); //[[A, B, C], ?]chars.sliding(3).force(); //force evaluation //[[A, B, C], [B, C, D], [C, D, E], [D, E, F], [E, F, G]]這兩種方法非常有用。 您可以通過滑動窗口查看數(shù)據(jù)(例如,計算移動平均值 )或?qū)⑵鋭澐譃榈乳L的存儲桶。
您可能會發(fā)現(xiàn)有用的最后一個有用的實用程序方法是scan() ,它會(當然是延遲地scan()迭代輸入流,并通過在輸入的前一個和當前元素上應(yīng)用函數(shù)來構(gòu)造輸出的每個元素。 代碼段值一千個字:
LazySeq<Integer> list = LazySeq.numbers(1).scan(0, (a, x) -> a + x);list.take(10).force(); //[0, 1, 3, 6, 10, 15, 21, 28, 36, 45] LazySeq.numbers(1)是自然數(shù)(1、2、3…)的序列。 scan()創(chuàng)建一個新的序列,從
0并為輸入的每個元素(自然數(shù))將其添加到自身的最后一個元素。 因此我們得到:[ 0 0+1 0+1+2 0+1+2+3 0+1+2+3+4 0+1+2+3+4+5 …]。 如果您想要一系列增長的字符串,只需替換幾種類型:
并享受這個美麗的三角形:
|\ |*\ |**\ |***\ |****\ |*****\ |******\ |*******\ |********\ |*********\或者(相同的輸出):
LazySeq.iterate("", s -> s + "*").map(s -> "|" + s + "\\").take(10).forEach(System.out::println);Java Collections框架的互操作性
LazySeq實現(xiàn)java.util.List接口,因此可以在許多地方使用。 此外,它還對集合(即流和集合)實現(xiàn)了Java 8增強:
lazySeq.stream().map(n -> n + 1).flatMap(n -> asList(0, n - 1).stream()).filter(n -> n != 0).substream(4, 18).limit(10).sorted().distinct().collect(Collectors.toList());但是,Java 8中的流是為了解決作為LazySeq (延遲評估)基礎(chǔ)的功能而創(chuàng)建的。 上面的示例將所有中間步驟推遲到調(diào)用collect()為止。 使用LazySeq您可以安全地跳過.stream()并直接處理序列:
lazySeq.map(n -> n + 1).flatMap(n -> asList(0, n - 1)).filter(n -> n != 0).slice(4, 18).limit(10).sorted().distinct();此外, LazySeq提供了特殊用途的收集器(請參閱: LazySeq.toLazySeq() ),即使與collect()一起使用也可以避免評估,這通常會強制進行完整的收集計算。
實施細節(jié)
每個懶惰序列都是圍繞急切計算的頭和懶洋洋地表示為函數(shù)的尾巴的思想構(gòu)建的。 這與經(jīng)典的單鏈列表遞歸定義非常相似:
class List<T> {private final T head;private final List<T> tail;//... }但是,在延遲序列的情況下, 尾部是函數(shù)而不是值。 該功能的調(diào)用會盡可能推遲:
class Cons<E> extends LazySeq<E> {private final E head;private LazySeq<E> tailOrNull;private final Supplier<LazySeq<E>> tailFun;@Overridepublic LazySeq<E> tail() {if (tailOrNull == null) {tailOrNull = tailFun.get();}return tailOrNull;}有關(guān)完整的實現(xiàn),請參見在創(chuàng)建時知道tail時使用的Cons.java和FixedCons.java (例如LazySeq.of(1, 2)與LazySeq.cons(1, () -> someTailFun()相對LazySeq.of(1, 2) ,例如LazySeq.of(1, 2) )。
陷阱和常見危險
下面介紹常見問題和誤解。
評估太多
使用無限序列的最大危險之一就是試圖對其進行完全評估,這顯然會導致無限計算。 無限序列背后的思想不是整體評估它,而是在不引入人為限制和意外復雜性的情況下,盡可能多地獲取它(請參見數(shù)據(jù)庫加載示例)。
但是,評估整個序列太容易遺漏了。 例如,調(diào)用LazySeq.size() 必須評估整個序列,并且將無限運行,最終填滿堆棧或堆(實現(xiàn)細節(jié))。 還有其他方法需要完全遍歷才能正常運行。 例如allMatch()確保所有元素都匹配給定謂詞。 有些方法甚至更危險,因為它們是否完成取決于序列中的數(shù)據(jù)。 例如,如果head匹配謂詞–或從不, anyMatch()可能會立即返回。
有時,我們可以使用更具確定性的方法來輕松避免昂貴的操作。 例如:
seq.size() <= 10 //BAD如果seq是無限的,則可能無法正常工作或非常慢。 但是,我們可以通過(更多)可預(yù)測的方式實現(xiàn)相同的目標:
seq.drop(10).isEmpty()請記住,惰性序列是不可變的(因此,我們實際上并不對seq突變), drop(n)通常為O(n)而isEmpty()為O(1) 。
如有疑問,請查閱源代碼或JavaDoc以確保您的操作不會過于急切地評估您的序列。 使用LazySeq ,也要非常小心,需要使用java.util.Collection或java.util.List 。
持有不必要的頭參考
惰性序列應(yīng)定義為記住已計算的元素。 您必須意識到這一點,否則您的序列(尤其是無限序列)將Swift填滿可用內(nèi)存。 但是,由于LazySeq只是一個奇特的鏈接列表,因此,如果您不再保留對head的引用(而僅保留中間的某個元素),則可以進行垃圾回收。 例如:
//LazySeq<Char> first = seq.take(10); seq = seq.drop(10);前十個元素被刪除,我們假設(shè)沒有任何東西引用過seq以前的內(nèi)容。 這使前十個元素有資格進行垃圾回收。 但是,如果我們?nèi)∠⑨尩谝恍?#xff0c;并保持參照老head在first ,JVM不會釋放任何內(nèi)存。 讓我們對此進行透視。 下面的代碼最終將拋出OutOfMemoryError因為infinite引用將保持序列的開頭,因此,到目前為止創(chuàng)建的所有元素:
LazySeq<Big> infinite = LazySeq.<Big>continually(Big::new); for (Big arr : infinite) {// }但是,通過內(nèi)聯(lián)對continually()調(diào)用或?qū)⑵涮崛〉椒椒ㄖ?#xff0c;此代碼可以完美運行(嗯,仍然可以永遠運行,但幾乎不使用內(nèi)存):
private LazySeq<Big> getContinually() {return LazySeq.<Big>continually(Big::new); }for (Big arr : getContinually()) {// }有什么不同? 每個循環(huán)都在下面使用迭代器。 LazySeqIterator下面的LazySeqIterator不會保留對舊head()的引用,因此,如果沒有其他引用該head的對象,則可以進行垃圾回收,當使用for-each時,請參見true javac輸出:
for (Iterator<Big> cur = getContinually().iterator(); cur.hasNext(); ) {final Big arr = cur.next();//... }TL; DR
您的序列在遍歷時會增加。 如果在另一端成長時保持一端,則最終會炸毀。 就像您在Hibernate中的一級緩存一樣,如果您在一個事務(wù)中加載過多。 僅根據(jù)需要使用。
轉(zhuǎn)換為純Java集合
轉(zhuǎn)換很簡單,但是很危險。 這是以上幾點的結(jié)果。 您可以通過調(diào)用toList()將惰性序列轉(zhuǎn)換為java.util.List :
LazySeq<Integer> even = LazySeq.numbers(0, 2); even.take(5).toList(); //[0, 2, 4, 6, 8]或使用具有更豐富API的Java 8中的Collector :
even.stream().limit(5).collect(Collectors.toSet()) //[4, 6, 0, 2, 8] 但是請記住,Java集合在定義上是有限的,因此請避免將惰性序列明確轉(zhuǎn)換為集合。 注意
LazySeq已經(jīng)是List ,因此是Iterable和Collection 。 它還具有高效的LazySeq.iterator() 。 如果可以,只需直接傳遞LazySeq實例即可。
性能,時間和空間復雜度
每個序列(空除外head()的head()總是很容易計算,因此對其進行快速O(1)訪問。 計算tail()可能占用從O(1) (如果已經(jīng)計算過)到無限時間的所有內(nèi)容。 以這個有效的流為例:
import static com.blogspot.nurkiewicz.lazyseq.LazySeq.cons; import static com.blogspot.nurkiewicz.lazyseq.LazySeq.continually;LazySeq<Integer> oneAndZeros = cons(1,() -> continually(0) ). filter(x -> (x > 0));它代表1后跟無窮多個0 s。 通過過濾所有正數(shù)( x > 0 ),我們得到了一個具有相同頭部的序列,但是尾部的過濾被延遲了(延遲)。 但是,如果現(xiàn)在我們不小心調(diào)用oneAndZeros.tail() , LazySeq將繼續(xù)計算該無限序列的越來越多,但是由于在初始1之后沒有正元素,因此該操作將永遠運行,最終拋出StackOverflowError或OutOfMemoryError (這是一個實施細節(jié))。
但是,如果您達到此狀態(tài),則可能是編程錯誤或濫用該庫。 通常tail()將接近O(1) 。 另一方面,如果您已經(jīng)“堆疊”了很多操作,則調(diào)用tail()會Swift一次又一次觸發(fā)它們,因此tail()運行時間在很大程度上取決于您的數(shù)據(jù)結(jié)構(gòu)。
LazySeq上的大多數(shù)操作都是O(1)因為它們是惰性的。 一些操作,例如get(n)或drop(n)都是O(n) ( n表示參數(shù),而不是序列長度)。 一般而言,運行時間將類似于正常的鏈表。
因為LazySeq記住單個鏈接列表中所有已經(jīng)計算出的值,所以內(nèi)存消耗始終為O(n) ,其中n n是已經(jīng)計算出的元素數(shù)。
故障排除
錯誤invalid target release: 1.8在Maven構(gòu)建期間invalid target release: 1.8
如果在maven生成過程中看到此錯誤消息:
[INFO] BUILD FAILURE ... [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq: Fatal error compiling: invalid target release: 1.8 -> [Help 1]這意味著您不使用Java 8進行編譯。 下載具有l(wèi)ambda支持的JDK 8,并讓maven使用它:
$ export JAVA_HOME=/path/to/jdk8我收到StackOverflowError或程序無限期掛起
使用LazySeq ,有時會出現(xiàn)StackOverflowError或OutOfMemoryError :
java.lang.StackOverflowErrorat sun.misc.Unsafe.allocateInstance(Native Method)at java.lang.invoke.DirectMethodHandle.allocateInstance(DirectMethodHandle.java:426)at com.blogspot.nurkiewicz.lazyseq.LazySeq.iterate(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq.lambda$0(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq$$Lambda$2.get(Unknown Source)at com.blogspot.nurkiewicz.lazyseq.Cons.tail(Cons.java:32)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)當使用可能無限的數(shù)據(jù)結(jié)構(gòu)時,必須小心。 避免調(diào)用必須 ( size() , allMatch() , minBy() , forEach() , reduce() ,…)或可以 ( filter() , distinct() ,…)遍歷整個序列以便給出正確值的操作結(jié)果。 有關(guān)更多示例和避免方法,請參閱陷阱 。
到期
質(zhì)量
該項目是作為練習開始的,尚未經(jīng)過戰(zhàn)斗驗證。 但是,健康的300多個單元測試套件 (測試代碼/生產(chǎn)代碼比率為3:1)可以保護質(zhì)量和功能正確性。 我還通過LazySeq尾部函數(shù)并驗證它們被盡可能少地調(diào)用來確保LazySeq盡可能地懶惰。
貢獻和錯誤報告
如果發(fā)現(xiàn)錯誤或缺少功能,請隨時打開新票證或開始拉取請求 。 我也很LazySeq在野外看到LazySeq更多有趣用法。
可能的改進
- 就像在FixedCons知道尾巴的情況下使用FixedCons一樣,請考慮將IterableCons包裝在一個節(jié)點中而不是建立FixedCons層次結(jié)構(gòu)的Iterable 。 這可以用于所有concat方法。
- 并行處理支持(實現(xiàn)分離器?)
執(zhí)照
該項目是在Apache許可證的 2.0版下發(fā)布的 。
翻譯自: https://www.javacodegeeks.com/2013/05/lazy-sequences-implementation-for-java-8.html
總結(jié)
以上是生活随笔為你收集整理的Java 8的惰性序列实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国寿安鑫盈360天到期赎回需要手续费吗?
- 下一篇: Java开发中的常见危险信号