一. Lambda定義(λ):
-- 匿名,它不像普通方法那樣有一個明確的名稱;
-- 函數,它不像普通方法那樣屬於某個特定的類,但和方法一樣,Lambda有參數列表、函數主體、返回類型或拋出異常列表:
-- 傳遞,Lambda可以作爲參數傳遞給方法或存儲在變量中:
-- 簡潔。
二. Lambda表達式結構:
1. 參數列表;
2. 箭頭:箭頭->把參數列表與Lambda主體分隔開;
3. Lambda主體:表達式就是Lambda表達式的例子。
三.Lambda基本語法:
(parameters) -> expression
或
(parameters) -> { statements; }
使用顯式返回語句時需要使用花括號“{}”。
eg: Lambda示例:
使用案例 | Lambda示例 |
無參數,返回void | () -> {} |
無參數,返回String | () -> "Raoul" |
無參數,返回String(利用顯式返回語句) | () -> { return "Result";} |
布爾表達式 | (List<String> list) -> list.isEmpty() |
創建對象 | () -> new Apple(10); |
消費一個對象 | (Apple apple) -> { System.out.println(a.getWeight()); } |
從一個對象中選擇/抽取 | (String s) -> s.length() |
組合兩個值 | (int a, int b) -> a * b |
比較兩個對象 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |
四. 在哪裏使用Lambda
可以在函數式接口上使用Lambda表達式。
函數式接口:只定義一個抽象方法的接口。
@FunctionalInterface:表明爲函數式接口,但它不是必須的。
(T, U) -> R表達式展示了應當如何思考函數描述符。左側代表了參數類型。這裏它代表一個函數,具有兩個參數,分別爲泛型T和U,返回類型爲R。
Java的泛型只能綁定到引用類型,當需要引用原始類型時因爲自動裝箱和拆箱機制時,裝箱後的值需要更多的內存,需要付出性能代價,爲了避免裝箱操作對Predicate<T>和Function<T, R>等通用函數式接口的原始類型特化:IntPredicate、IntToLongFunction等。
Java8中的常用函數式接口:
函數式接口 | 函數描述符 | 原始類型特化 |
Predicate<T> | T -> boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | T -> void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T, R> | T -> R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> | () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | T -> T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T, T) -> T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L, R> | (L, R) -> boolean | |
BiConsumer<T, U> | (T, U) -> void | ObjectIntConsumer<T>, ObjectLongConsumer<T>, ObjectDoubleConsumer<T> |
BiFunction<T, U, R> | (T, U) -> R | ToIntBiFunction<T, U>, ToLongBiFunction<T, U>, ToDoubleBiFunction<T, U> |
Runnable | () -> void |
當需要Lambda表達式拋出異常時,有兩種方式:
-- 自己編寫新的函數式接口,並聲明受檢異常(任何函數式接口都不允許拋出受檢異常);
-- 將Lambda包在一個try/catch塊中。
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
Function<BufferedReader, String> f = (BufferedReader b) -> { try { return b.readLine(); } catch (IOException e) { throw new RuntimeException(e); } };
當Lambda表達式拋出一個異常時,throws語句也必須與Lambda所指類型相匹配。
如果Lambda的主體是一個語句表達式,它就和一個返回void的函數描述符兼容(當然需要參數列表也兼容)。
eg:儘管List的add方法返回的是boolean,但以下兩行都是合法的:
//Predicate返回了一個boolean Predicate<String> p = s -> list.add(s); //Consumer返回了一個void Consumer<String> b = s -> list.add(s);
Lambda類型推斷:Java編譯器會從上下文(目標類型)中推斷出用什麼函數式接口來配合Lambda表達式,所以也能推斷出適合Lambda的簽名,因爲函數描述符可以通過目標類型來得到。這樣就可以在Lambda中省去標註參數類型,當參數只有一個時還可以省去參數的括號。
Lambda使用局部變量的限制:Lambda表達式對值封閉,而不是對變量封閉。Lambda表達式引用的局部變量必須是final的,只能有一次賦值。
這是因爲實例變量是儲存在堆中,而局部變量是儲存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程可能會在分配該變量的線程將這個變量收回之後,去訪問該變量。因此,Java在訪問自由局部變量時,實際上是在訪問他的副本而不是訪問原始變量。
Lambda方法引用:
方法引用就是Lambda的快捷寫法。目標引用放在分隔符::前面,方法的名稱放在後面。
eg:
Lambda表達式 | 等效的方法引用 |
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
如何構建方法引用:
主要有三類:
-- 指向靜態方法的放方法引用;
-- 指向任意類型實例方法的方法引用;
-- 指向現有對象的實例方法的方法引用。
Lambda構造函數引用:
對於一個現有構造函數,可以利用它的名稱和關鍵字new來創建它的一個引用:ClassName::new。它的功能與靜態方法的引用類似。
Lambda表達式 | 等價構造方法引用Lambda表達式 |
Supplier<Apple> c1 = Apple::new; Apple a1 = c1.get(); | Supplier<Apple> c1 = () -> new Apple(); Apple a1 = c1.get(); |
Function<Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply(110); | Function<Integer, Apple> c2 = (weight) -> new Apple(weight); Apple a2 = c2.apply(110); |
BiFunction<String, Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply("green", 110); | BiFunction<String, Integer, Apple> c2 = (color, weight) -> new Apple(color, weight); Apple a2 = c2.apply("green", 110); |
複合Lambda表達式的有用方法:
複合類型 | 方法 | 說明 | 舉例 |
比較器複合 | reversed() | 逆序 | //按重量遞減排序 inventory.sort(comparing(Apple::getWeight)) .reversed(); |
thenComparing | 比較器鏈(接受一個函數作爲參數,如果兩個對象用第一個Comparator比較之後是一樣的,就提供第二個Comparator | //兩個蘋果一樣重時按國家排序 inventory.sort(comparing(Apple::getWeight)) .reversed(). thenComparing(Apple::getCountry); | |
謂詞複合 (and和or方法的優先級是按照在表達式鏈中的位置,從左向右確定的) | negate | 非 | //產生現有對象redApple的非 Predicate<Apple> notRedApple = redApple.negate(); |
and | 將兩個Lambda用and組合起來 | //一個蘋果既是紅色又比較重(鏈接兩個謂詞來生成一個Predicate對象) Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150); | |
or | 要麼 | //要麼重(150g以上)的紅蘋果,要麼是綠蘋果 Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor())); | |
函數複合 | andThen | 先對輸入應用一個給定函數,再對輸出應用另一個函數 | //等同於數學上的g(f(x)),返回4 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); |
compose | 先把給定的函數用作compose的參數裏面給的那個函數,然後再把函數本身用於結果 | //等同於數學上的f(g(x)),返回3 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); |