利用Lambda表示式進行Java中的惰性求值

banq發表於2018-08-28
在java中,懶性求值也稱懶計算lazy evaluation功能可能被忽視了(實際上,在語言層面,它幾乎僅限於最小求值的實現) - 例如Scala等高階語言區分了按值呼叫和按名稱呼叫,或引入像lazy這樣的專用關鍵字。

雖然Java 8透過提供Lazy Sequence概念(我們都知道它為java.util.stream.Stream而生)實現了改進 ,但今天我們將跳過這一點,關注引入Lambda表示式是如何給我們帶來一種新的輕量級方法。

在scala中實現Lambda支援的惰求值

每當我們想惰性計算Scala中的方法引數時,我們就可以採用“按名稱呼叫”方式。

讓我們建立一個簡單的foo方法,接受一個String例項並返回String:

def foo(b: String): String = b

現在,如果我們想讓b懶計算,我們可以利用call-by-name語法並簡單地在b的型別宣告中新增兩個符號:
def foo(b:=> String):String = b

如果我們試圖javap的反向工程生成的* .class檔案,我們會看到:

Compiled from "LazyFoo.scala"
public final class LazyFoo {
    public static java.lang.String foo(scala.Function0<java.lang.String>);
    Code:   
        0: getstatic #17 // Field LazyFoo$.MODULE$:LLazyFoo$;
        3: aload_0
        4: invokevirtual #19 // Method LazyFoo$.foo:(Lscala/Function0;)Ljava/lang/String;
        7: areturn
}
<p class="indent">


傳遞給我們方法的引數不再是String,而是一個  Function0 <String> - 這使得可以使用惰性求值表示式 - 只要我們不呼叫它,就不會觸發計算- 就是如此容易。

在Java中
當我們需要懶計算。返回一個T時,我們只要按照上面思路,將計算用等同於Function0的 Supplier<T> 例項包裝:

Integer v1 = 42; // eager

Supplier<Integer> v2 = () -> 42; // lazy
<p class="indent">

如果我們需要從資料庫獲得結果,那將更加實用:

Integer v1 = compute(); //eager

Supplier<Integer> value = () -> compute(); // lazy
<p class="indent">

這樣,只有value方法被呼叫時,才會從資料庫查詢計算。

惰性求值也可以作為輸入引數,只有方法體內使用這個函式才會計算。

private static int computeLazily(Supplier<Integer> value) {
    // ...
}
<p class="indent">


如果仔細觀察Java 8中引入的API,您
會注意到這種模式經常被使用,如OptionalorElseGet 是等同於 OptionalorElse惰性求值方法,如果沒我們介紹的模式實現, Optional會毫無作用。

執行緒安全
遺憾的是,這種簡單的方法存在缺陷 - 每次函式呼叫都會觸發計算 - 不僅多執行緒環境是這樣,同一執行緒連續呼叫也是這樣的情況 ,當然 只要我們知道這個特點就可以了,合理應用這個技術就好。

有記憶的惰性求值

如前所述,基於lambda的方法在某些情況下可能是有缺陷的,因為求值的結果永遠不會被記憶化。

為了解決這個問題,我們需要構建一個專用工具,比方說Lazy工具類:

@RequiredArgsConstructor
public class NaiveLazy<T> implements Supplier<T> { 
    private final Supplier<T> supplier;
    private T value;

    @Override
    public T get() {
        if (value == null) {
            value = supplier.get();
        }
        return value;
    }
}
<p class="indent">


上面並不是執行緒安全的。

幸運的是,使其成為執行緒安全只需要確保多個執行緒在嘗試獲取值時不會觸發相同的計算 - 這可以透過使用雙重檢查鎖定模式輕鬆實現(我們可以簡單地在get()方法上同步,但這會引入不必要的爭用):

@RequiredArgsConstructor
public class Lazy<T> implements Supplier<T> {
    private final Supplier<T> supplier;
    private volatile T value;
 
    @Override
    public T get() {
        if (value == null) {
            synchronized (this) {
                if (value == null) {
                    value = supplier.get();
                }
            }
        }
        return value;
    }
}
<p class="indent">


現在我們在Java中實現了惰求值的全功能實現。由於它沒有在語言級別實現,因此需要支付與建立新物件相關的額外成本。

banq評: 可惜了,不是無鎖記憶性懶計算,在jdon框架中我N多年前使用LAMX實現了無鎖懶計算。
原文:
https://4comprehension.com/leveraging-lambda-expressions-for-lazy-evaluation-in-java/

[該貼被banq於2018-08-28 20:19修改過]

[該貼被banq於2018-08-28 20:20修改過]

相關文章