摘要:例如上一章的蘋果謂詞接口只有一個抽象方法的接口能讓把整個表達式作為函數式接口的實例。這個函數式接口是用來接收一個對象并對其進行處理。
Lambda 管中窺豹
可以把 Lambda 表達式理解為簡潔地可傳遞的匿名函數的一種方式:沒有名稱,有參數列表、函數主體、返回類型和可能的異常。理論上講,匿名函數做不到的事,Lambda 也做不了。后者只是讓前者可讀性更強,寫得更輕松。
回顧上一章最后的那個 Lambda 表達式
(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
我們可以發現 Lambda 可以劃分為三個部分:
參數 (Apple apple1)
箭頭 ->
主體 "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
Lambda 的基本語法是這樣的:
(parameters) -> expression
(parameters) -> { statements; }
在哪里以及如何使用 Lambda我們可以在函數式接口中使用 Lambda。
函數式接口就是只定義一個抽象方法的接口。
例如上一章的蘋果謂詞接口
public interface ApplePredicate { boolean test(Apple apple); }
只有一個抽象方法的接口能讓 Lambda 把整個表達式作為函數式接口的實例。匿名內部類一樣可以完成同樣的事,只不過很笨拙。
JDK 中的 Runnable 接口
@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。我們可以使用一下練練手。
Predicate這個和上一章最后我們自己寫的那個函數式接口,兩者完全一樣,都是用來做條件測試的謂詞接口。
通過謂詞過濾泛型集合
public staticList filter(List list, Predicate predicate) { List result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; }
過濾掉字符串集合中空字符串
ListstringList = new ArrayList<>(); stringList.add("hello"); stringList.add(""); stringList.add("lambda"); // Lambda 實現 Predicate stringPredicate = (String s) -> !s.isEmpty(); // 匿名實現 Predicate stringPredicate1 = new Predicate () { @Override public boolean test(String s) { return !s.isEmpty(); } }; List result = filter(stringList, stringPredicate);
這樣空字符串就會被過濾掉,只剩下 hello、lambda。
Consumer這個函數式接口是用來接收一個對象并對其進行處理。
遍歷一個泛型集合
public staticvoid forEach(List list, Consumer consumer) { for (T t : list) { consumer.accept(t); } }
取出字符串集合里面的對象并打印輸出
// Lambda 實現 Consumerconsumer = (String s) -> System.out.println(s); // 匿名實現 Consumer consumer1 = new Consumer () { @Override public void accept(String s) { System.out.println(s); } }; forEach(stringList, consumer);
這樣就會打印輸出 hello、 、lambda。
Function這個函數式接口是用來接收一個對象并映射到另一個對象。
接收一個集合對象并返回另一個集合對象
public staticList map(List list, Function function) { List result = new ArrayList<>(); for (T t : list) { result.add(function.apply(t)); } return result; }
接收一個字符串集合并映射成其長度的整型集合后返回
// Lambda 實現 Functionfunction = (String s) -> s.length(); // 匿名實現 Function function1 = new Function () { @Override public Integer apply(String s) { return s.length(); } }; List integerList = map(stringList, function);
這樣 hello、 、lambda 就分別對應其長度 5、0、6。
方法引用方法引用可以看作對特定 Lambda 的一種快捷寫法,本質上依然是 Lambda。它的基本思想是,如果一個 Lambda 代表的僅僅是 直接調用 這個方法而不是 描述如何去調用 這個方法,那最好還是用名稱來調用它。
例如在上一章的蘋果實例中我們需要用謂詞判斷是否成熟
// 普通 Lambda 寫法 PredicateapplePredicate1 = (Apple apple) -> apple.isAging(); // 方法引用寫法 Predicate applePredicate2 = Apple::isAging;
我們可以把方法引用看作對 僅僅涉及單一方法 的 Lambda 的語法糖。
構造函數引用對于一個現有的構造函數,我們可以利用它的名稱和 new 來創建它的一個引用:ClassName::new。
// 普通 Lambda 創建對象 // SupplierappleSupplier = () -> new Apple(); // 構造函數引用創建無參對象 Supplier appleSupplier = Apple::new; // 獲取實例 Apple apple1 = appleSupplier.get(); // 構造函數引用創建有一個參數對象 Function appleFunction = Apple::new; // 獲取實例 Apple apple2 = appleFunction.apply("red"); // 構造函數引用創建有兩個參數對象 BiFunction appleBiFunction = Apple::new; // 獲取實例 Apple apple3 = appleBiFunction.apply("red", 1);
那么當參數有很多的時候怎么辦呢?我們可以自定義一個函數式接口進行處理。
三個參數的構造函數引用接口
public interface TriFunction{ R apply(T t, U u, V v); }
調用也是類似的
// 構造函數引用創建有三個參數對象 TriFunctionLambda 和方法引用實戰 傳遞代碼appleTriFunction = Apple::new; // 獲取實例 Apple apple4 = appleTriFunction.apply("red", 1, true);
我們如果要對一個蘋果集合按照重量從小到大排序,首先肯定要進行判斷大小,然后對其進行排序。按照我們已經經過一章多的學習,應該能很輕松地構建一個解決方案。
1、創建比較器
public class AppleComparator implements Comparator{ @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } }
2、調用 List 已經實現好的排序方法
public class Main { public static void main(String[] args) { ListappleList = 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.1、No.2 appleList.add(apple); // 依照重量排序后 appleList 的順序會變成 No.2、No.1 appleList.sort(new AppleComparator()); } }
這只是簡單的一個通過傳遞比較器來進行排序。下面我們會用匿名類來實現上一章學習的 應對不斷變化的需求。
使用匿名類到這一步其實已經算得上符合正常軟件工程設計了,可以舍去 AppleComparator 這樣的實現方式。
appleList.sort(new Comparator使用 Lambda 表達式() { @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使用方法引用,傳遞進 sort() 的 Comparator 會自動定義泛型為 Apple,所以 Lambda 也可以自動推斷為 Comparator 的 compare() 傳入的類型。 appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
使用方法引用還可以更加讓人通俗易懂。
Comparator 有個靜態方法 comparing 可以接收 Function 函數式接口,用作比較依據。
// 傳入的 Function 本質是將蘋果(Apple)映射成了蘋果的重量(Integer) // Functionfunction = (Apple o1) -> o1.getWeight(); // 方法引用后 Function function = Apple::getWeight; // 傳入到 comparing 靜態方法 Comparator.comparing(function);
所以最后可以簡化到極致
// 代表按照蘋果的重量進行比較后排序 appleList.sort(Comparator.comparing(Apple::getWeight));復合 Lambda 表達式的有用方法
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,我們可以以此創建復雜的謂詞。
選出蘋果不是紅的
PredicateisRed = (Apple o1) -> "red".equalsIgnoreCase(o1.getColor()); Predicate noRed = isRed.negate();
選出蘋果不是紅的且成熟的
PredicatenoRedAndIsAging = noRed.and(Apple::getAging);
選出蘋果不是紅的且成熟的或重量大于1
PredicatenoRedAndIsAgingOrHeavey = noRedAndIsAging.or((Apple o1) -> o1.getWeight() > 1);
總結起來,除了 isRed 謂詞要在第一步寫,其余的地方都可以一句話寫完
Predicatepredicate = 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 Functionf = x -> x + 1; // g(x) = x * 2 Function g = x -> x * 2; // h(x) = f(g(x)) Function 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)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/70186.html
摘要:語言是強類型面向對象的語言,所以必須提供一種數據類型作為表達式的返回值類型符合中函數格式的定義符合面向對象規則,所以最終表達式要有一個映射成對象的過程。定一個函數式接口我們在接口里定義了一個沒有參數返回值的抽象方法。 在JAVA中,Lambda 表達式(Lambda expression)是一個抽象方法的實現。這個抽象方法必須是在接口中聲明的,而且實現類只需要實現這一個抽象方法,我們稱...
摘要:表達式的主要作用就是代替匿名內部類的煩瑣語法。從這點來看,表達式的代碼塊與匿名內部類的方法體是相同的。與匿名內部類相似的是,由于表達式訪問了局部變量,該局部變量相當于與一個隱式的修飾,因此不允許對局部變量重新賦值。 函數式接口 函數式接口(Functional Interface)就是一個只有一個抽象方法(可以包含多個默認方法或多個static方法)的普通接口,可以被隱式轉換為lamb...
摘要:在支持一類函數的語言中,表達式的類型將是函數。匿名函數的返回類型與該主體表達式一致如果表達式的主體包含一條以上語句,則表達式必須包含在花括號中形成代碼塊。注意,使用表達式的方法不止一種。 摘要:此篇文章主要介紹 Java8 Lambda 表達式產生的背景和用法,以及 Lambda 表達式與匿名類的不同等。本文系 OneAPM 工程師編譯整理。 Java 是一流的面向對象語言,除了部分簡...
摘要:表達式簡介表達式是一個匿名函數對于而言并不很準確,但這里我們不糾結這個問題。如果表達式的正文有一條以上的語句必須包含在大括號代碼塊中,且表達式的返回值類型要與匿名函數的返回類型相同。 版權聲明:本文由吳仙杰創作整理,轉載請注明出處:https://segmentfault.com/a/1190000009186509 1. 引言 在 Java 8 以前,若我們想要把某些功能傳遞給某些方...
摘要:初體驗下面進入本文的正題表達式。接下來展示表達式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構造器引用進一步簡化了表達式的書寫,但是個人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請聯系 tengfei@ifeve.com 本文主要記錄自己學習Java8的歷程,方便大家一起探討和自己的備忘。因為本人也是剛剛開始學習Java8,所以文中肯定有錯誤和理解偏...
摘要:函數式編程與面向對象編程表達式函數柯里化高階函數之劍什么是表達式例子定義表達式是一個匿名函數,表達式基于數學中的演算得名,直接對應于其中的抽象,是一個匿名函數,即沒有函數名的函數。 函數式編程與面向對象編程[1]: Lambda表達式 函數柯里化 高階函數.md 之劍 2016.5.2 11:19:09 什么是lambda表達式 例子 For example, in Lisp the...
閱讀 2657·2019-08-30 15:53
閱讀 2879·2019-08-29 16:20
閱讀 1086·2019-08-29 15:10
閱讀 1026·2019-08-26 10:58
閱讀 2198·2019-08-26 10:49
閱讀 637·2019-08-26 10:21
閱讀 707·2019-08-23 18:30
閱讀 1640·2019-08-23 15:58