利用 Lambda 表示式實現 Java 中的惰性求值
Java 中惰性求值的潛能,完全被忽視了(在語言層面上,它僅被用來實現 短路求值 )。更先進的語言,如 Scala,區分了傳值呼叫與傳名呼叫,或者引入了 lazy 這樣的關鍵字。
儘管 Java 8 透過延遲佇列的實現(java.util.stream.Stream)在惰性求值的方面有些改進,但是我們會先跳過 Stream,而把重點放在如何使用 lambda 表示式實現一個輕量級的惰性求值。
基於 lambda 的惰性求值
Scala
當我們想對 Scala 中的方法引數進行惰性求值時,我們用“傳名呼叫”來實現。
讓我們建立一個簡單的 foo 方法,它接受一個 String 示例,然後返回這個 String:
deffoo(b:String):String=b
一切都是馬上返回的,跟 Java 中的一樣。如果我們想讓 b 的計算延遲,可以使用傳名呼叫的語法,只要在 b 的型別宣告上加兩個符號,來看:
deffoo(b:=> String):String=b
如果用 javap 反編譯上面生成的 *.class 檔案,可以看到:
Compiled from "LazyFoo.scala"
publicfinalclassLazyFoo {
publicstaticjava.lang.String foo(scala.Function0);
Code:
0: getstatic #17 // Field LazyFoo.MODULE:LLazyFoo$;
3: aload_0
4: invokevirtual #19 // Method LazyFoo$.foo:(Lscala/Function0;)Ljava/lang/String;
7: areturn
}
看起來傳給這個函式的引數不再是一個String了,而是變成了一個Function0,這使得對這個表示式進行延遲計算變得可能 —— 只要我們不去呼叫他,計算就不會被觸發。Scala 中的惰性求值就是這麼簡單。
使用 Java
現在,如果我們需要延遲觸發一個返回T的計算,我們可以複用上面的思路,將計算包裝為一個返回Supplier例項的 Java Function0 :
Integer v1 = 42; // eager
Supplier<Integer> v2 = () -> 42; // lazy
如果需要花費較長時間才能從函式中獲得結果,上面這個方法會更加實用:
Integer v1 = compute(); //eager
Supplier<Integer> value = () -> compute(); // lazy
同樣的,這次傳入一個方法作為引數:
privatestaticintcomputeLazily(Supplier value) {
// ...
}
如果仔細觀察 Java 8 中新增的 API,你會注意到這種模式使用得特別頻繁。一個最顯著的例子就是 Optional#orElseGet ,Optional#orElse 的惰性求值版本。
如果不使用這種模式的話,那麼 Optional 就沒什麼用處了… 或許吧。當然,我們不會滿足於 suppliers 。我們可以用同樣的方法複用所有 functional 介面。
執行緒安全和快取
不幸的是,上面這個簡單的方法是有缺陷的:每次呼叫都會觸發一次計算。不僅多執行緒的呼叫有這個缺陷,同一個執行緒連續呼叫多次也有這個缺陷。不過,如果我們清楚這個缺陷,並且合理的使用這個技術,那就沒什麼問題。
使用快取的惰性求值
剛才已經提到,基於 lambda 表示式的方法在一些情況下是有缺陷的,因為返回值沒有儲存起來。為了修復這個缺陷,我們需要構造一個專用的工具,讓我們叫它 Lazy :
publicclassLazy { ... }
這個工具需要自身同時儲存Supplier和 返回值T。
@RequiredArgsConstructor
publicclassNaiveLazy {
privatefinalSupplier supplier;
privateT value;
publicT get() {
if(value ==null) {
value = supplier.get();
}
returnvalue;
}
}
就是這麼簡單。注意上面的程式碼僅僅是一個概念模型,暫時還不是執行緒安全的。
幸運的是,如果想讓它變得執行緒安全,只需要保證不同的執行緒在獲取返回值的時候不會觸發同樣的計算。這可以簡單的透過雙重檢查鎖定機制來實現(我們不能直接在 get() 方法上加鎖,這會引入不必要的競爭):
@RequiredArgsConstructor
publicclassLazy {
privatefinalSupplier supplier;
privatevolatileT value;
publicT get() {
if(value ==null) {
synchronized(this) {
if(value ==null) {
value = supplier.get();
}
}
}
returnvalue;
}
}
現在,我們有了一個完整的 Java 惰性求值的函式化實現。由於它不是在語言的層面實現的,需要付出建立一個新物件的代價。
更深入的討論
當然,我們不會就此打住,我們可以進一步的最佳化這個工具。比如,透過引入一個惰性的filter()/flatMap()/map()方法,可以讓它使用起來更加流暢,並且組合性更強:
public Lazy map(Function mapper) {
returnnewLazy<>(() -> mapper.apply(this.get()));
}
public Lazy flatMap(Function> mapper) {
returnnewLazy<>(() -> mapper.apply(this.get()).get());
}
作者:Java大生
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/758/viewspace-2818636/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 利用Lambda表示式進行Java中的惰性求值Java
- Java 中的 Lambda 表示式Java
- Java中Lambda表示式的使用Java
- Java 8 中的 lambda 表示式Java
- Java 8中的Lambda表示式最佳實踐Java
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- Java8中的Lambda表示式Java
- Java中Lambda表示式的應用Java
- Java的Lambda表示式Java
- Java中lambda表示式詳解Java
- Java表示式求值引擎 - AviatorJava
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- Java Lambda表示式Java
- Java中Lambda表示式的進化之路Java
- <七>lambda表示式實現原理
- Java之lambda表示式Java
- Java 8 Lambda 表示式Java
- java 8 lambda表示式Java
- Java Lambda 表示式初探Java
- Java8的Lambda表示式Java
- 棧在表示式求值中的應用
- 3.2.5 表示式求值
- 如何用 JavaScript 實現一個陣列惰性求值庫JavaScript陣列
- 探索Java語言與JVM中的Lambda表示式JavaJVM
- Java 基礎 —— Lambda 表示式Java
- Java lambda表示式基本使用Java
- Java8-Lambda表示式Java
- java8 lambda表示式Java
- 掌握 Java 8 Lambda 表示式Java
- Java筆記:Lambda表示式Java筆記
- C#中的Lambda表示式和表示式樹C#
- 使用棧實現表示式求值,運用棧計算
- Java語言與JVM中的Lambda表示式全解JavaJVM
- Java8特性詳解 lambda表示式(二):流式處理中的lambdaJava
- 深入探索 Java 8 Lambda 表示式Java
- java8特性-lambda表示式Java