中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

利用 Lambda 表達式實現(xiàn) Java 中的惰性求值

2018-08-22    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

Java 中惰性求值的潛能,完全被忽視了(在語言層面上,它僅被用來實現(xiàn)?短路求值?)。更先進的語言,如 Scala,區(qū)分了傳值調(diào)用與傳名調(diào)用,或者引入了 lazy 這樣的關(guān)鍵字。

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

基于 lambda 的惰性求值

Scala

當(dāng)我們想對 Scala 中的方法參數(shù)進行惰性求值時,我們用“傳名調(diào)用”來實現(xiàn)。

讓我們創(chuàng)建一個簡單的 foo 方法,它接受一個 String 示例,然后返回這個 String:

def foo(b: String): String = b

一切都是馬上返回的,跟 Java 中的一樣。如果我們想讓 b 的計算延遲,可以使用傳名調(diào)用的語法,只要在 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

}

看起來傳給這個函數(shù)的參數(shù)不再是一個?String?了,而是變成了一個?Function0,這使得對這個表達式進行延遲計算變得可能 —— 只要我們不去調(diào)用他,計算就不會被觸發(fā)。Scala 中的惰性求值就是這么簡單。

使用 Java

現(xiàn)在,如果我們需要延遲觸發(fā)一個返回?T?的計算,我們可以復(fù)用上面的思路,將計算包裝為一個返回?Supplier?實例的 Java Function0 :

Integer v1 = 42; // eager

Supplier<Integer> v2 = () -> 42; // lazy

如果需要花費較長時間才能從函數(shù)中獲得結(jié)果,上面這個方法會更加實用:

Integer v1 = compute(); //eager

Supplier<Integer> value = () -> compute(); // lazy

同樣的,這次傳入一個方法作為參數(shù):

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

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

如果不使用這種模式的話,那么 Optional 就沒什么用處了… 或許吧。當(dāng)然,我們不會滿足于 suppliers 。我們可以用同樣的方法復(fù)用所有 functional 接口。

線程安全和緩存

不幸的是,上面這個簡單的方法是有缺陷的:每次調(diào)用都會觸發(fā)一次計算。不僅多線程的調(diào)用有這個缺陷,同一個線程連續(xù)調(diào)用多次也有這個缺陷。不過,如果我們清楚這個缺陷,并且合理的使用這個技術(shù),那就沒什么問題。

使用緩存的惰性求值

剛才已經(jīng)提到,基于 lambda 表達式的方法在一些情況下是有缺陷的,因為返回值沒有保存起來。為了修復(fù)這個缺陷,我們需要構(gòu)造一個專用的工具,讓我們叫它 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;

    }

}

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

幸運的是,如果想讓它變得線程安全,只需要保證不同的線程在獲取返回值的時候不會觸發(fā)同樣的計算。這可以簡單的通過雙重檢查鎖定機制來實現(xiàn)(我們不能直接在 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;
    }
}

現(xiàn)在,我們有了一個完整的 Java 惰性求值的函數(shù)化實現(xiàn)。由于它不是在語言的層面實現(xiàn)的,需要付出創(chuàng)建一個新對象的代價。

更深入的討論

當(dāng)然,我們不會就此打住,我們可以進一步的優(yōu)化這個工具。比如,通過引入一個惰性的?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));
}

優(yōu)化永無止境。

我們也可以暴露一個方便的工廠方法:

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);

你可以看到,只要作為調(diào)用鏈底層的 #get 方法沒有被調(diào)用,那么什么計算也不會觸發(fā)。

Null 的處理

某些情況下,null 會被當(dāng)做有意義的值。不過它與我們的實現(xiàn)有沖突 —— 一個有意義的 null 值被當(dāng)做一個未初始化的值,這不太合適。

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

除此之外,明確禁止 null 作為返回值也是一個好辦法,比如:

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

回收不再使用的 Supplier

有些讀者可能已經(jīng)注意到了,結(jié)果計算完畢之后,supplier 就不再使用了,但是它仍然占據(jù)一些資源。

解決辦法就是把 Supplier 標(biāo)記為非 final 的,一旦結(jié)果計算完畢,就把它置為 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);
    }
}

以上的代碼也可以在?GitHub?上找到。

原文鏈接: dzone 翻譯: ImportNew.com - yizhe
譯文鏈接: http://www.importnew.com/29540.html
[ 轉(zhuǎn)載請保留原文出處、譯者和譯文鏈接。]

關(guān)于作者: yizhe

(新浪微博:@今天我行嗎

查看yizhe的更多文章 >>

標(biāo)簽: 安全 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:運行 tail | grep 后按下 Ctrl – C 的過程中發(fā)生了什么

下一篇:誤刪除dev下特殊文件怎么辦