利用 Lambda 表達式實現 Java 中的惰性求值

Java 中惰性求值的潛能,徹底被忽視了(在語言層面上,它僅被用來實現 短路求值 )。更先進的語言,如 Scala,區分了傳值調用與傳名調用,或者引入了 lazy 這樣的關鍵字。java

儘管 Java 8 經過延遲隊列的實現(java.util.stream.Stream)在惰性求值的方面有些改進,可是咱們會先跳過 Stream,而把重點放在如何使用 lambda 表達式實現一個輕量級的惰性求值。面試

基於 lambda 的惰性求值緩存

Scala安全

當咱們想對 Scala 中的方法參數進行惰性求值時,咱們用「傳名調用」來實現。多線程

讓咱們建立一個簡單的 foo 方法,它接受一個 String 示例,而後返回這個 String:架構

def foo(b: String): String = bapp

一切都是立刻返回的,跟 Java 中的同樣。若是咱們想讓 b 的計算延遲,可使用傳名調用的語法,只要在 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

}

看起來傳給這個函數的參數再也不是一個 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

一樣的,此次傳入一個方法做爲參數:

private static int computeLazily(Supplier<Integer> value) {

    // ...

}

若是仔細觀察 Java 8 中新增的 API,你會注意到這種模式使用得特別頻繁。一個最顯著的例子就是 Optional#orElseGet ,Optional#orElse 的惰性求值版本。

若是不使用這種模式的話,那麼 Optional 就沒什麼用處了… 或許吧。固然,咱們不會知足於 suppliers 。咱們能夠用一樣的方法複用全部 functional 接口。

線程安全和緩存

不幸的是,上面這個簡單的方法是有缺陷的:每次調用都會觸發一次計算。不只多線程的調用有這個缺陷,同一個線程連續調用屢次也有這個缺陷。不過,若是咱們清楚這個缺陷,而且合理的使用這個技術,那就沒什麼問題。

使用緩存的惰性求值

剛纔已經提到,基於 lambda 表達式的方法在一些狀況下是有缺陷的,由於返回值沒有保存起來。爲了修復這個缺陷,咱們須要構造一個專用的工具,讓咱們叫它 Lazy :

public class Lazy<T> { ... }

這個工具須要自身同時保存 Supplier 和 返回值 T

@RequiredArgsConstructor

public class NaiveLazy<T> {

  private final Supplier<T> supplier;

  private T value;

  public T get() {

  if (value == null) {

     value = supplier.get();

}

  return value;

}

}

就是這麼簡單。注意上面的代碼僅僅是一個概念模型,暫時還不是線程安全的。

幸運的是,若是想讓它變得線程安全,只須要保證不一樣的線程在獲取返回值的時候不會觸發一樣的計算。這能夠簡單的經過雙重檢查鎖定機制來實現(咱們不能直接在 get() 方法上加鎖,這會引入沒必要要的競爭):

@RequiredArgsConstructor

public class Lazy<T> {

    private final Supplier<T> supplier;

    private volatile T value;

    public T get() {

        if (value == null) {

            synchronized (this) {

                if (value == null) {

                    value = supplier.get();

                }

            }

        }

       return value;

    }

}

如今,咱們有了一個完整的 Java 惰性求值的函數化實現。因爲它不是在語言的層面實現的,須要付出建立一個新對象的代價。

更深刻的討論

固然,咱們不會就此打住,咱們能夠進一步的優化這個工具。好比,經過引入一個惰性的 filter()/flatMap()/map() 方法,可讓它使用起來更加流暢,而且組合性更強:

public <R> Lazy<R> map(Function<T, R> mapper) {

    return new Lazy<>(() -> mapper.apply(this.get()));

}

 public <R> Lazy<R> flatMap(Function<T, Lazy<R>> mapper) {

    return new Lazy<>(() -> mapper.apply(this.get()).get());

public Lazy<Optional<T>> filter(Predicate<T> predicate) {

    return new Lazy<>(() -> Optional.of(get()).filter(predicate));

}

優化永無止境。

咱們也能夠暴露一個方便的工廠方法:

public static <T> Lazy<T> of(Supplier<T> supplier) {

    return new Lazy<>(supplier);

}

實際使用上:

Lazy.of(() -> compute(42))

  .map(s -> compute(13))

  .flatMap(s -> lazyCompute(15))

  .filter(v -> v > 0);

你能夠看到,只要做爲調用鏈底層的 #get 方法沒有被調用,那麼什麼計算也不會觸發。

Null 的處理

某些狀況下,null 會被當作有意義的值。不過它與咱們的實現有衝突 —— 一個有意義的 null 值被當作一個未初始化的值,這不太合適。

解決方法也很簡單,直接把這種可能的結果包裝到一個 Optional 實例裏返回。

除此以外,明確禁止 null 做爲返回值也是一個好辦法,好比:

value = Objects.requireNonNull(supplier.get());

回收再也不使用的 Supplier

有些讀者可能已經注意到了,結果計算完畢以後,supplier 就再也不使用了,可是它仍然佔據一些資源。

解決辦法就是把 Supplier 標記爲非 final 的,一旦結果計算完畢,就把它置爲 null。

完整的例子

public class Lazy<T> {

    private transient Supplier<T> supplier;

    private volatile T value;

    public Lazy(Supplier<T> supplier) {

        this.supplier = Objects.requireNonNull(supplier);

    }

    public T get() {

        if (value == null) {

            synchronized (this) {

                if (value == null) {

                    value = Objects.requireNonNull(supplier.get());

                    supplier = null;

                }

            }

        }

        return value;

    }

    public <R> Lazy<R> map(Function<T, R> mapper) {

        return new Lazy<>(() -> mapper.apply(this.get()));

    }

    public <R> Lazy<R> flatMap(Function<T, Lazy<R>> mapper) {

        return new Lazy<>(() -> mapper.apply(this.get()).get());

    }

    public Lazy<Optional<T>> filter(Predicate<T> predicate) {

        return new Lazy<>(() -> Optional.of(get()).filter(predicate));

    }

    public static <T> Lazy<T> of(Supplier<T> supplier) {

        return new Lazy<>(supplier);

    }

}

歡迎工做一到五年的Java工程師朋友們加入Java架構開發:點擊連接加入羣聊【Java架構交流羣】:https://jq.qq.com/?_wv=1027&k=5DmXtYD

本羣提供免費的學習指導 架構資料 以及免費的解答

不懂得問題均可以在本羣提出來 以後還會有職業生涯規劃以及面試指導

同時你們能夠多多關注一下小編 你們一塊兒學習進步

相關文章
相關標籤/搜索