能夠把 Lambda 表達式理解爲簡潔地可傳遞的匿名函數的一種方式:沒有名稱,有參數列表、函數主體、返回類型和可能的異常。理論上講,匿名函數作不到的事,Lambda 也作不了。後者只是讓前者可讀性更強,寫得更輕鬆。java
回顧上一章最後的那個 Lambda 表達式express
(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
咱們能夠發現 Lambda 能夠劃分爲三個部分:app
(Apple apple1)
->
"green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
Lambda 的基本語法是這樣的:ide
咱們能夠在函數式接口中使用 Lambda。函數
函數式接口就是隻定義一個抽象方法的接口。學習
例如上一章的蘋果謂詞接口測試
public interface ApplePredicate { boolean test(Apple apple); }
只有一個抽象方法的接口能讓 Lambda 把整個表達式做爲函數式接口的實例。匿名內部類同樣能夠完成一樣的事,只不過很笨拙。設計
JDK 中的 Runnable 接口code
@FunctionalInterface public interface Runnable { public abstract void run(); }
兩種實現方式對象
Runnable runnable1 = () -> System.out.println("Hello Word!"); Runnable runnable2 = new Runnable() { @Override public void run() { System.out.println("Hello Word!"); } };
這兩鍾實現結果是同樣的。函數式接口的返回類型基本上就是 Lambda 的返回類型。
函數式接口通常均可以被 @FunctionalInterface
註解,這個註解就如同它的名字同樣表明這個接口是函數式接口。而且它和 @Override
同樣只是讓編譯期判斷是否正確,運行期無關,並非必需的。若是在一個接口中定義了多個抽象方法,並加上這個註解再編譯的話,編譯器便會給你的報錯,由於這樣的接口已經不符合函數式接口的定義了。
JDK 自己也自帶了幾個函數式接口,好比 Predicate、Consumer、Function。咱們可使用一下練練手。
這個和上一章最後咱們本身寫的那個函數式接口,二者徹底同樣,都是用來作條件測試的謂詞接口。
經過謂詞過濾泛型集合
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; }
過濾掉字符串集合中空字符串
List<String> stringList = new ArrayList<>(); stringList.add("hello"); stringList.add(""); stringList.add("lambda"); // Lambda 實現 Predicate<String> stringPredicate = (String s) -> !s.isEmpty(); // 匿名實現 Predicate<String> stringPredicate1 = new Predicate<String>() { @Override public boolean test(String s) { return !s.isEmpty(); } }; List<String> result = filter(stringList, stringPredicate);
這樣空字符串就會被過濾掉,只剩下 hello
、lambda
。
這個函數式接口是用來接收一個對象並對其進行處理。
遍歷一個泛型集合
public static <T> void forEach(List<T> list, Consumer<T> consumer) { for (T t : list) { consumer.accept(t); } }
取出字符串集合裏面的對象並打印輸出
// Lambda 實現 Consumer<String> consumer = (String s) -> System.out.println(s); // 匿名實現 Consumer<String> consumer1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; forEach(stringList, consumer);
這樣就會打印輸出 hello
、 、lambda
。
這個函數式接口是用來接收一個對象並映射到另外一個對象。
接收一個集合對象並返回另外一個集合對象
public static <T, R> List<R> map(List<T> list, Function<T, R> function) { List<R> result = new ArrayList<>(); for (T t : list) { result.add(function.apply(t)); } return result; }
接收一個字符串集合並映射成其長度的整型集合後返回
// Lambda 實現 Function<String, Integer> function = (String s) -> s.length(); // 匿名實現 Function<String, Integer> function1 = new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }; List<Integer> integerList = map(stringList, function);
這樣 hello
、 、lambda
就分別對應其長度 5
、0
、6
。
方法引用能夠看做對特定 Lambda 的一種快捷寫法,本質上依然是 Lambda。它的基本思想是,若是一個 Lambda 表明的僅僅是 直接調用
這個方法而不是 描述如何去調用
這個方法,那最好仍是用名稱來調用它。
例如在上一章的蘋果實例中咱們須要用謂詞判斷是否成熟
// 普通 Lambda 寫法 Predicate<Apple> applePredicate1 = (Apple apple) -> apple.isAging(); // 方法引用寫法 Predicate<Apple> applePredicate2 = Apple::isAging;
咱們能夠把方法引用看做對 僅僅涉及單一方法
的 Lambda 的語法糖。
對於一個現有的構造函數,咱們能夠利用它的名稱和 new
來建立它的一個引用:ClassName::new
。
// 普通 Lambda 建立對象 // Supplier<Apple> appleSupplier = () -> new Apple(); // 構造函數引用建立無參對象 Supplier<Apple> appleSupplier = Apple::new; // 獲取實例 Apple apple1 = appleSupplier.get(); // 構造函數引用建立有一個參數對象 Function<String, Apple> appleFunction = Apple::new; // 獲取實例 Apple apple2 = appleFunction.apply("red"); // 構造函數引用建立有兩個參數對象 BiFunction<String, Integer, Apple> appleBiFunction = Apple::new; // 獲取實例 Apple apple3 = appleBiFunction.apply("red", 1);
那麼當參數有不少的時候怎麼辦呢?咱們能夠自定義一個函數式接口進行處理。
三個參數的構造函數引用接口
public interface TriFunction<T, U, V, R> { R apply(T t, U u, V v); }
調用也是相似的
// 構造函數引用建立有三個參數對象 TriFunction<String, Integer, Boolean, Apple> appleTriFunction = Apple::new; // 獲取實例 Apple apple4 = appleTriFunction.apply("red", 1, true);
咱們若是要對一個蘋果集合按照重量從小到大排序,首先確定要進行判斷大小,而後對其進行排序。按照咱們已經通過一章多的學習,應該能很輕鬆地構建一個解決方案。
一、建立比較器
public class AppleComparator implements Comparator<Apple> { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } }
二、調用 List 已經實現好的排序方法
public class Main { public static void main(String[] args) { List<Apple> appleList = new ArrayList<>(); // 重量爲2的成熟紅蘋果 No.1 Apple apple = new Apple(); apple.setColor("red"); apple.setWeight(2); apple.setAging(true); appleList.add(apple); // 重量爲1的未成熟綠蘋果 No.2 apple = new Apple(); apple.setColor("green"); apple.setWeight(1); apple.setAging(false); // 如今 appleList 的順序是放入的順序 No.一、No.2 appleList.add(apple); // 依照重量排序後 appleList 的順序會變成 No.二、No.1 appleList.sort(new AppleComparator()); } }
這只是簡單的一個經過傳遞比較器來進行排序。下面咱們會用匿名類來實現上一章學習的 應對不斷變化的需求
。
到這一步其實已經算得上符合正常軟件工程設計了,能夠捨去 AppleComparator
這樣的實現方式。
appleList.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } });
緊接着咱們能夠更加高效地用 Lambda 實現。
appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));
咱們還能夠進一步簡化代碼。Java 編譯器會從目標類型自動推斷出適合 Lambda 的返回類型。所以能夠省略對傳入參數的類型定義。
// appleList 的類型定義是 List<Apple>,傳遞進 sort() 的 Comparator<T> 會自動定義泛型爲 Apple,因此 Lambda 也能夠自動推斷爲 Comparator 的 compare() 傳入的類型。 appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
使用方法引用還能夠更加讓人通俗易懂。
Comparator 有個靜態方法 comparing 能夠接收 Function 函數式接口,用做比較依據。
// 傳入的 Function 本質是將蘋果(Apple)映射成了蘋果的重量(Integer) // Function<Apple, Integer> function = (Apple o1) -> o1.getWeight(); // 方法引用後 Function<Apple, Integer> function = Apple::getWeight; // 傳入到 comparing 靜態方法 Comparator.comparing(function);
因此最後能夠簡化到極致
// 表明按照蘋果的重量進行比較後排序 appleList.sort(Comparator.comparing(Apple::getWeight));
Java 8提供了容許進行復合的方法。好比咱們可讓兩個謂詞之間作 or 操做,組成一個更增強大的謂詞。
若是想要從大到小排序蘋果的重量(默認的 sort() 是從小到大排序)
appleList.sort(Comparator.comparing(Apple::getWeight).reversed());
可是若是兩個的蘋果重量同樣,咱們須要再根據顏色或者是否成熟來排序呢?
咱們可使用 thenComparing 方法
appleList.sort(Comparator // 從大到小排列蘋果的重量 .comparing(Apple::getWeight).reversed() // 而後按照顏色字母順序 .thenComparing(Apple::getColor) // 而後按照是否成熟 .thenComparing(Apple::getAging));
這樣就能夠建立一個比較器鏈了。
謂詞接口包括三個方法:negate、and 和 or,咱們能夠以此建立複雜的謂詞。
選出蘋果不是紅的
Predicate<Apple> isRed = (Apple o1) -> "red".equalsIgnoreCase(o1.getColor()); Predicate<Apple> noRed = isRed.negate();
選出蘋果不是紅的且成熟的
Predicate<Apple> noRedAndIsAging = noRed.and(Apple::getAging);
選出蘋果不是紅的且成熟的或重量大於1
Predicate<Apple> noRedAndIsAgingOrHeavey = noRedAndIsAging.or((Apple o1) -> o1.getWeight() > 1);
總結起來,除了 isRed 謂詞要在第一步寫,其他的地方均可以一句話寫完
Predicate<Apple> predicate = noRed .and(Apple::getAging) .or((Apple o1) -> o1.getWeight() > 1);
這樣從簡單的 Lambda 出發,能夠構建更加複雜的表達式,但讀起來會更加輕鬆。注意,and 和 or 的是按照鏈中的位置執行。
Function 接口包括兩個方法:andThen 和 compose,它們都會返回一個 Function 的實例。
咱們能夠用 Function 定義三個函數 f(x)、g(x)和 g(x),先看看 andThen()
// f(x) = x + 1 Function<Integer, Integer> f = x -> x + 1; // g(x) = x * 2 Function<Integer, Integer> g = x -> x * 2; // h(x) = f(g(x)) Function<Integer, Integer> h = f.andThen(g);
傳入 x 進行運算
// 結果爲4 int result = h.apply(1);
compose()
// h(x) = g(f(x)) h = f.compose(g); // 結果爲3 result = h.apply(1);
第三章的東西有點多,須要反覆消化理解。
Java 8 實戰 第三章 Lambda 表達式 讀書筆記
歡迎加入咖啡館的春天(338147322)。