溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Lambda表達(dá)式是什么

發(fā)布時(shí)間:2021-10-20 17:34:09 來源:億速云 閱讀:126 作者:柒染 欄目:大數(shù)據(jù)

這篇文章給大家介紹Lambda表達(dá)式是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

1. Lambda簡介

可以把Lambda表達(dá)式理解為簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表、函數(shù)主體、返回類型,可能還有一個(gè)可以拋出的異常列表。

  • 匿名——我們說匿名,是因?yàn)樗幌衿胀ǖ姆椒菢佑幸粋€(gè)明確的名稱:寫得少而想得多!

  • 函數(shù)——我們說它是函數(shù),是因?yàn)長ambda函數(shù)不像方法那樣屬于某個(gè)特定的類。但和方法一樣,Lambda有參數(shù)列表、函數(shù)主體、返回類型,還可能有可以拋出的異常列表。

  • 傳遞——Lambda表達(dá)式可以作為參數(shù)傳遞給方法或存儲(chǔ)在變量中。

  • 簡潔——無需像匿名類那樣寫很多模板代碼。

2. Lambda寫法

(parameters) -> expression 或 (parameters) -> { statements; } eg:(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); Lambda表達(dá)式有三個(gè)部分:

  • 參數(shù)列表——這里它采用了Comparator中compare方法的參數(shù),兩個(gè)Apple。

  • 箭頭——箭頭->把參數(shù)列表與Lambda主體分隔開。

  • Lambda主體——比較兩個(gè)Apple的重量。表達(dá)式就是Lambda的返回值了。

Lambda表達(dá)式是什么

3. 函數(shù)式接口和函數(shù)描述符

函數(shù)式接口就是只定義一個(gè)抽象方法的接口。接口上標(biāo)有@FunctionalInterface表示該接口會(huì)設(shè)計(jì)成 一個(gè)函數(shù)式接口,如果你用@FunctionalInterface定義了一個(gè)接口,而它卻不是函數(shù)式接口的話,編譯器將返回一個(gè)提示原因的錯(cuò)誤。接口現(xiàn)在還可以擁有默認(rèn)方法(即在類沒有對(duì)方法進(jìn)行實(shí)現(xiàn)時(shí), 其主體為方法提供默認(rèn)實(shí)現(xiàn)的方法)。哪怕有很多默認(rèn)方法,只要接口只定義了一個(gè)抽象方法,它就仍然是一個(gè)函數(shù)式接口。

函數(shù)式接口的抽象方法的簽名就是Lambda表達(dá)式的簽名。我們將這種抽象方法叫作:函數(shù)描述符。例如,Runnable接口可以看作一個(gè)什么也不接受什么也不返回(void)的函數(shù)的簽名,因?yàn)樗挥幸粋€(gè)叫作run的抽象方法,這個(gè)方法什么也不接受,什么也不返回(void)。

4. 三種常用的函數(shù)式接口

4.1 Predicate

/**
 * Represents a predicate (boolean-valued function) of one argument.
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 * @param <T> the type of the input to the predicate
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

Predicate的英文示意是:謂詞。 Predicate接口定義了一個(gè)名叫test的抽象方法,它接受泛型T對(duì)象,并返回一個(gè)boolean。

4.2 Consumer

/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
* @param <T> the type of the input to the operation
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
   /**
    * Performs this operation on the given argument.
    * @param t the input argument
    */
   void accept(T t);
}

Consumer的英文示意是:消費(fèi)者。 Consumer接口定義了一個(gè)名叫accept的抽象方法,它接受泛型T對(duì)象,并沒有返回任何值。

4.3 Function

/**
* Represents a function that accepts one argument and produces a result.
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
   /**
    * Applies this function to the given argument.
    * @param t the function argument
    * @return the function result
    */
   R apply(T t);
}

Function的英文示意是:功能。 Function接口定義了一個(gè)名叫apply的抽象方法,它接受泛型T對(duì)象,并返回一個(gè)泛型R的對(duì)象。

Java還有一個(gè)自動(dòng)裝箱機(jī)制來幫助程序員執(zhí)行這一任務(wù):裝箱和拆箱操作是自動(dòng)完成的。但這在性能方面是要付出代價(jià)的。裝箱后的值本質(zhì)上就是把原始類型包裹起來,并保存在堆里。因此,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取被包裹的原始值。Java 8為我們前面所說的函數(shù)式接口帶來了一個(gè)專門的版本,以便在輸入和輸出都是原始類型時(shí)避免自動(dòng)裝箱的操作。

4.4 Java 8中的常用函數(shù)式接口

Lambda表達(dá)式是什么

5. 類型檢查、類型推斷以及限制

5.1 類型檢查

Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(比如,接受它傳遞的方法的參數(shù),或接受它的值的局部變量)中Lambda表達(dá)式需要的類型稱為目標(biāo)類型。 Lambda表達(dá)式是什么 類型檢查過程可以分解為如下所示。

  • 首先,你要找出filter方法的聲明。

  • 第二,要求它是Predicate<Apple>(目標(biāo)類型)對(duì)象的第二個(gè)正式參數(shù)。

  • 第三,Predicate<Apple>是一個(gè)函數(shù)式接口,定義了一個(gè)叫作test的抽象方法。

  • 第四,test方法描述了一個(gè)函數(shù)描述符,它可以接受一個(gè)Apple,并返回一個(gè)boolean.

  • 最后,filter的任何實(shí)際參數(shù)都必須匹配這個(gè)要求。

這段代碼是有效的,因?yàn)槲覀兯鶄鬟f的Lambda表達(dá)式也同樣接受Apple為參數(shù),并返回一個(gè) boolean。請(qǐng)注意,如果Lambda表達(dá)式拋出一個(gè)異常,那么抽象方法所聲明的throws語句也必 須與之匹配。有了目標(biāo)類型的概念,同一個(gè)Lambda表達(dá)式就可以與不同的函數(shù)式接口聯(lián)系起來,只要它 們的抽象方法簽名能夠兼容。比如,前面提到的Callable和PrivilegedAction,這兩個(gè)接口都代表著什么也不接受且返回一個(gè)泛型T的函數(shù)。 因此,下面兩個(gè)賦值是有效的:

Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;

特殊的void兼容規(guī)則 如果一個(gè)Lambda的主體是一個(gè)語句表達(dá)式, 它就和一個(gè)返回void的函數(shù)描述符兼容(當(dāng)然需要參數(shù)列表也兼容)。 例如,以下兩行都是合法的,盡管List的add方法返回了一個(gè) boolean,而不是Consumer上下文(T -> void)所要求的void:

// Predicate返回了一個(gè)boolean 
Predicate<String> p = s -> list.add(s); 
// Consumer返回了一個(gè)void 
Consumer<String> b = s -> list.add(s);

5.2 類型推斷

Java編譯器會(huì)從上下文(目標(biāo)類型)推斷出用什么函數(shù)式接 口來配合Lambda表達(dá)式,這意味著它也可以推斷出適合Lambda的簽名,因?yàn)楹瘮?shù)描述符可以通過目標(biāo)類型來得到。這樣做的好處在于,編譯器可以了解Lambda表達(dá)式的參數(shù)類型,這樣就可以在Lambda語法中省去標(biāo)注參數(shù)類型。

// 沒有類 型推斷
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 
// 有類型推斷
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

5.3 使用局部變量

Lambda表達(dá)式 也允許使用自由變量(不是參數(shù),而是在外層作用域中定義的變量),就像匿名類一樣。 它們被 稱作捕獲Lambda。 Lambda捕獲的局部變量必須顯式聲明為final, 或事實(shí)上是final。換句話說,Lambda表達(dá)式只能捕獲指派給它們的局部變量一次。

  • 第一,實(shí)例變量和局部變量背后的實(shí)現(xiàn)有一 個(gè)關(guān)鍵不同。實(shí)例變量都存儲(chǔ)在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個(gè)線程中使用的,則使用Lambda的線程,可能會(huì)在分配該變量的線程將這個(gè)變量收回之后,去訪問該變量。因此,Java在訪問自由局部變量時(shí),實(shí)際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區(qū)別了——因此就有了這個(gè)限制。

  • 第二,這一限制不鼓勵(lì)你使用改變外部變量的典型命令式編程模式(我們會(huì)在以后的各章中 解釋,這種模式會(huì)阻礙很容易做到的并行處理)。

6. 方法引用

方法引用可以被看作僅僅調(diào)用特定方法的Lambda的一種快捷 寫法,方法引用看作針對(duì)僅僅涉及單一方法的Lambda的語法糖。目標(biāo)引用放在分隔符::前,方法的名稱放在后面。方法引用主要有三類:

  • (1) 指向靜態(tài)方法的方法引用(例如Integer的parseInt方法,寫作Integer::parseInt)。

  • (2) 指向任意類型實(shí)例方法的方法引用(例如String的length方法,寫作 String::length)。

  • (3) 指向現(xiàn)有對(duì)象的實(shí)例方法的方法引用(假設(shè)你有一個(gè)局部變量expensiveTransaction 用于存放Transaction類型的對(duì)象,它支持實(shí)例方法getValue,那么你就可以寫expensive- Transaction::getValue)。

對(duì)于一個(gè)現(xiàn)有構(gòu)造函數(shù),你可以利用它的名稱和關(guān)鍵字new來創(chuàng)建它的一個(gè)引用: ClassName::new。它的功能與指向靜態(tài)方法的引用類似。
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
     
這就等價(jià)于:
Supplier<Apple> c1 = () -> new Apple(); // 利用默認(rèn)構(gòu)造函數(shù)創(chuàng)建 Apple的Lambda表達(dá)式
Apple a1 = c1.get(); // 調(diào)用Supplier的get方法 將產(chǎn)生一個(gè)新的Apple

7. 復(fù)合Lambda表達(dá)式的有用方法

7.1 比較器復(fù)合

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
// 逆序 按重量遞 減排序
inventory.sort(comparing(Apple::getWeight).reversed());
// 比較器鏈 按重量遞減排序;兩個(gè)蘋果一樣重時(shí),進(jìn)一步按國家排序
inventory.sort(comparing(Apple::getWeight)
         .reversed()
         .thenComparing(Apple::getCountry));

7.2 謂詞復(fù)合

// 產(chǎn)生現(xiàn)有Predicate 對(duì)象redApple的非
Predicate<Apple> notRedApple = redApple.negate();
// 鏈接兩個(gè)謂詞來生成另 一個(gè)Predicate對(duì)象  一個(gè)蘋果既是紅色又比較重
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
// 鏈接Predicate的方法來構(gòu)造更復(fù)雜Predicate對(duì)象 表達(dá)要么是重(150克以上)的紅蘋果,要么是綠蘋果
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) 
                                            .or(a -> "green".equals(a.getColor()));
請(qǐng)注意,and和or方法是按照在表達(dá)式鏈中的位置,從左向右確定優(yōu) 先級(jí)的。因此,a.or(b).and(c)可以看作(a || b) && c。

7.3 函數(shù)復(fù)合

andThen方法會(huì)返回一個(gè)函數(shù),它先對(duì)輸入應(yīng)用一個(gè)給定函數(shù),再對(duì)輸出應(yīng)用另一個(gè)函數(shù)。 比如,
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
數(shù)學(xué)上會(huì)寫作g(f(x))或(g o f)(x)
這將返回4

compose方法,先把給定的函數(shù)用作compose的參數(shù)里面給的那個(gè)函 數(shù),然后再把函數(shù)本身用于結(jié)果。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1);
數(shù)學(xué)上會(huì)寫作f(g(x))或(f o g)(x) 
這將返回3

以下是你應(yīng)從本章中學(xué)到的關(guān)鍵概念。

  • Lambda表達(dá)式可以理解為一種匿名函數(shù):它沒有名稱,但有參數(shù)列表、函數(shù)主體、返回類型,可能還有一個(gè)可以拋出的異常的列表。

  • Lambda表達(dá)式讓你可以簡潔地傳遞代碼。

  • 函數(shù)式接口就是僅僅聲明了一個(gè)抽象方法的接口。

  • 只有在接受函數(shù)式接口的地方才可以使用Lambda表達(dá)式。

  • Lambda表達(dá)式允許你直接內(nèi)聯(lián),為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并且將整個(gè)表達(dá)式作為函數(shù)式接口的一個(gè)實(shí)例。

  • Java 8自帶一些常用的函數(shù)式接口,放在java.util.function包里,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>,如表3-2所述。

  • 為了避免裝箱操作,對(duì)Predicate<T>和Function<T, R>等通用函數(shù)式接口的原始類型特化:IntPredicate、IntToLongFunction等。

  • 環(huán)繞執(zhí)行模式(即在方法所必需的代碼中間,你需要執(zhí)行點(diǎn)兒什么操作,比如資源分配 和清理)可以配合Lambda提高靈活性和可重用性。

  • Lambda表達(dá)式所需要代表的類型稱為目標(biāo)類型。

  • 方法引用讓你重復(fù)使用現(xiàn)有的方法實(shí)現(xiàn)并直接傳遞它們。

  • Comparator、Predicate和Function等函數(shù)式接口都有幾個(gè)可以用來結(jié)合Lambda表達(dá)式的默認(rèn)方法。

關(guān)于Lambda表達(dá)式是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI