您好,登錄后才能下訂單哦!
這篇文章給大家介紹Lambda表達式是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
可以把Lambda表達式理解為簡潔地表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常列表。
匿名——我們說匿名,是因為它不像普通的方法那樣有一個明確的名稱:寫得少而想得多!
函數——我們說它是函數,是因為Lambda函數不像方法那樣屬于某個特定的類。但和方法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
傳遞——Lambda表達式可以作為參數傳遞給方法或存儲在變量中。
簡潔——無需像匿名類那樣寫很多模板代碼。
(parameters) -> expression 或 (parameters) -> { statements; }
eg:(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Lambda表達式有三個部分:
參數列表——這里它采用了Comparator中compare方法的參數,兩個Apple。
箭頭——箭頭->把參數列表與Lambda主體分隔開。
Lambda主體——比較兩個Apple的重量。表達式就是Lambda的返回值了。
函數式接口就是只定義一個抽象方法的接口。接口上標有@FunctionalInterface
表示該接口會設計成 一個函數式接口,如果你用@FunctionalInterface
定義了一個接口,而它卻不是函數式接口的話,編譯器將返回一個提示原因的錯誤。接口現在還可以擁有默認方法(即在類沒有對方法進行實現時, 其主體為方法提供默認實現的方法)。哪怕有很多默認方法,只要接口只定義了一個抽象方法,它就仍然是一個函數式接口。
函數式接口的抽象方法的簽名就是Lambda表達式的簽名。我們將這種抽象方法叫作:函數描述符。例如,Runnable接口可以看作一個什么也不接受什么也不返回(void)的函數的簽名,因為它只有一個叫作run的抽象方法,這個方法什么也不接受,什么也不返回(void)。
/** * 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接口定義了一個名叫test的抽象方法,它接受泛型T對象,并返回一個boolean。
/** * 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的英文示意是:消費者。 Consumer接口定義了一個名叫accept的抽象方法,它接受泛型T對象,并沒有返回任何值。
/** * 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接口定義了一個名叫apply的抽象方法,它接受泛型T對象,并返回一個泛型R的對象。
Java還有一個自動裝箱機制來幫助程序員執行這一任務:裝箱和拆箱操作是自動完成的。但這在性能方面是要付出代價的。裝箱后的值本質上就是把原始類型包裹起來,并保存在堆里。因此,裝箱后的值需要更多的內存,并需要額外的內存搜索來獲取被包裹的原始值。Java 8為我們前面所說的函數式接口帶來了一個專門的版本,以便在輸入和輸出都是原始類型時避免自動裝箱的操作。
Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(比如,接受它傳遞的方法的參數,或接受它的值的局部變量)中Lambda表達式需要的類型稱為目標類型。 類型檢查過程可以分解為如下所示。
首先,你要找出filter方法的聲明。
第二,要求它是Predicate<Apple>(目標類型)對象的第二個正式參數。
第三,Predicate<Apple>是一個函數式接口,定義了一個叫作test的抽象方法。
第四,test方法描述了一個函數描述符,它可以接受一個Apple,并返回一個boolean.
最后,filter的任何實際參數都必須匹配這個要求。
這段代碼是有效的,因為我們所傳遞的Lambda表達式也同樣接受Apple為參數,并返回一個 boolean。請注意,如果Lambda表達式拋出一個異常,那么抽象方法所聲明的throws語句也必 須與之匹配。有了目標類型的概念,同一個Lambda表達式就可以與不同的函數式接口聯系起來,只要它 們的抽象方法簽名能夠兼容。比如,前面提到的Callable和PrivilegedAction,這兩個接口都代表著什么也不接受且返回一個泛型T的函數。 因此,下面兩個賦值是有效的:
Callable<Integer> c = () -> 42; PrivilegedAction<Integer> p = () -> 42;
特殊的void兼容規則 如果一個Lambda的主體是一個語句表達式, 它就和一個返回void的函數描述符兼容(當然需要參數列表也兼容)。 例如,以下兩行都是合法的,盡管List的add方法返回了一個 boolean,而不是Consumer上下文(T -> void)所要求的void:
// Predicate返回了一個boolean Predicate<String> p = s -> list.add(s); // Consumer返回了一個void Consumer<String> b = s -> list.add(s);
Java編譯器會從上下文(目標類型)推斷出用什么函數式接 口來配合Lambda表達式,這意味著它也可以推斷出適合Lambda的簽名,因為函數描述符可以通過目標類型來得到。這樣做的好處在于,編譯器可以了解Lambda表達式的參數類型,這樣就可以在Lambda語法中省去標注參數類型。
// 沒有類 型推斷 Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); // 有類型推斷 Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
Lambda表達式 也允許使用自由變量(不是參數,而是在外層作用域中定義的變量),就像匿名類一樣。 它們被 稱作捕獲Lambda。 Lambda捕獲的局部變量必須顯式聲明為final, 或事實上是final。換句話說,Lambda表達式只能捕獲指派給它們的局部變量一次。
第一,實例變量和局部變量背后的實現有一 個關鍵不同。實例變量都存儲在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線程將這個變量收回之后,去訪問該變量。因此,Java在訪問自由局部變量時,實際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區別了——因此就有了這個限制。
第二,這一限制不鼓勵你使用改變外部變量的典型命令式編程模式(我們會在以后的各章中 解釋,這種模式會阻礙很容易做到的并行處理)。
方法引用可以被看作僅僅調用特定方法的Lambda的一種快捷 寫法,方法引用看作針對僅僅涉及單一方法的Lambda的語法糖。目標引用放在分隔符::前,方法的名稱放在后面。方法引用主要有三類:
(1) 指向靜態方法的方法引用(例如Integer的parseInt方法,寫作Integer::parseInt)。
(2) 指向任意類型實例方法的方法引用(例如String的length方法,寫作 String::length)。
(3) 指向現有對象的實例方法的方法引用(假設你有一個局部變量expensiveTransaction 用于存放Transaction類型的對象,它支持實例方法getValue,那么你就可以寫expensive- Transaction::getValue)。
對于一個現有構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用: ClassName::new。它的功能與指向靜態方法的引用類似。 Supplier<Apple> c1 = Apple::new; Apple a1 = c1.get(); 這就等價于: Supplier<Apple> c1 = () -> new Apple(); // 利用默認構造函數創建 Apple的Lambda表達式 Apple a1 = c1.get(); // 調用Supplier的get方法 將產生一個新的Apple
Comparator<Apple> c = Comparator.comparing(Apple::getWeight); // 逆序 按重量遞 減排序 inventory.sort(comparing(Apple::getWeight).reversed()); // 比較器鏈 按重量遞減排序;兩個蘋果一樣重時,進一步按國家排序 inventory.sort(comparing(Apple::getWeight) .reversed() .thenComparing(Apple::getCountry));
// 產生現有Predicate 對象redApple的非 Predicate<Apple> notRedApple = redApple.negate(); // 鏈接兩個謂詞來生成另 一個Predicate對象 一個蘋果既是紅色又比較重 Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150); // 鏈接Predicate的方法來構造更復雜Predicate對象 表達要么是重(150克以上)的紅蘋果,要么是綠蘋果 Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor())); 請注意,and和or方法是按照在表達式鏈中的位置,從左向右確定優 先級的。因此,a.or(b).and(c)可以看作(a || b) && c。
andThen方法會返回一個函數,它先對輸入應用一個給定函數,再對輸出應用另一個函數。 比如, 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); 數學上會寫作g(f(x))或(g o f)(x) 這將返回4 compose方法,先把給定的函數用作compose的參數里面給的那個函 數,然后再把函數本身用于結果。 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); 數學上會寫作f(g(x))或(f o g)(x) 這將返回3
以下是你應從本章中學到的關鍵概念。
Lambda表達式可以理解為一種匿名函數:它沒有名稱,但有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常的列表。
Lambda表達式讓你可以簡潔地傳遞代碼。
函數式接口就是僅僅聲明了一個抽象方法的接口。
只有在接受函數式接口的地方才可以使用Lambda表達式。
Lambda表達式允許你直接內聯,為函數式接口的抽象方法提供實現,并且將整個表達式作為函數式接口的一個實例。
Java 8自帶一些常用的函數式接口,放在java.util.function包里,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>,如表3-2所述。
為了避免裝箱操作,對Predicate<T>和Function<T, R>等通用函數式接口的原始類型特化:IntPredicate、IntToLongFunction等。
環繞執行模式(即在方法所必需的代碼中間,你需要執行點兒什么操作,比如資源分配 和清理)可以配合Lambda提高靈活性和可重用性。
Lambda表達式所需要代表的類型稱為目標類型。
方法引用讓你重復使用現有的方法實現并直接傳遞它們。
Comparator、Predicate和Function等函數式接口都有幾個可以用來結合Lambda表達式的默認方法。
關于Lambda表達式是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。