摘要:例如上一章的蘋果謂詞接口只有一個(gè)抽象方法的接口能讓把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例。這個(gè)函數(shù)式接口是用來接收一個(gè)對(duì)象并對(duì)其進(jìn)行處理。
Lambda 管中窺豹
可以把 Lambda 表達(dá)式理解為簡潔地可傳遞的匿名函數(shù)的一種方式:沒有名稱,有參數(shù)列表、函數(shù)主體、返回類型和可能的異常。理論上講,匿名函數(shù)做不到的事,Lambda 也做不了。后者只是讓前者可讀性更強(qiáng),寫得更輕松。
回顧上一章最后的那個(gè) Lambda 表達(dá)式
(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
我們可以發(fā)現(xiàn) Lambda 可以劃分為三個(gè)部分:
參數(shù) (Apple apple1)
箭頭 ->
主體 "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()
Lambda 的基本語法是這樣的:
(parameters) -> expression
(parameters) -> { statements; }
在哪里以及如何使用 Lambda我們可以在函數(shù)式接口中使用 Lambda。
函數(shù)式接口就是只定義一個(gè)抽象方法的接口。
例如上一章的蘋果謂詞接口
public interface ApplePredicate { boolean test(Apple apple); }
只有一個(gè)抽象方法的接口能讓 Lambda 把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例。匿名內(nèi)部類一樣可以完成同樣的事,只不過很笨拙。
JDK 中的 Runnable 接口
@FunctionalInterface public interface Runnable { public abstract void run(); }
兩種實(shí)現(xiàn)方式
Runnable runnable1 = () -> System.out.println("Hello Word!"); Runnable runnable2 = new Runnable() { @Override public void run() { System.out.println("Hello Word!"); } };
這兩鐘實(shí)現(xiàn)結(jié)果是一樣的。函數(shù)式接口的返回類型基本上就是 Lambda 的返回類型。
函數(shù)式接口一般都可以被 @FunctionalInterface 注解,這個(gè)注解就如同它的名字一樣代表這個(gè)接口是函數(shù)式接口。并且它和 @Override 一樣只是讓編譯期判斷是否正確,運(yùn)行期無關(guān),并不是必需的。如果在一個(gè)接口中定義了多個(gè)抽象方法,并加上這個(gè)注解再編譯的話,編譯器便會(huì)給你的報(bào)錯(cuò),因?yàn)檫@樣的接口已經(jīng)不符合函數(shù)式接口的定義了。
使用函數(shù)式接口JDK 本身也自帶了幾個(gè)函數(shù)式接口,比如 Predicate、Consumer、Function。我們可以使用一下練練手。
Predicate這個(gè)和上一章最后我們自己寫的那個(gè)函數(shù)式接口,兩者完全一樣,都是用來做條件測(cè)試的謂詞接口。
通過謂詞過濾泛型集合
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 實(shí)現(xiàn) Predicate stringPredicate = (String s) -> !s.isEmpty(); // 匿名實(shí)現(xiàn) Predicate stringPredicate1 = new Predicate () { @Override public boolean test(String s) { return !s.isEmpty(); } }; List result = filter(stringList, stringPredicate);
這樣空字符串就會(huì)被過濾掉,只剩下 hello、lambda。
Consumer這個(gè)函數(shù)式接口是用來接收一個(gè)對(duì)象并對(duì)其進(jìn)行處理。
遍歷一個(gè)泛型集合
public staticvoid forEach(List list, Consumer consumer) { for (T t : list) { consumer.accept(t); } }
取出字符串集合里面的對(duì)象并打印輸出
// Lambda 實(shí)現(xiàn) Consumerconsumer = (String s) -> System.out.println(s); // 匿名實(shí)現(xiàn) Consumer consumer1 = new Consumer () { @Override public void accept(String s) { System.out.println(s); } }; forEach(stringList, consumer);
這樣就會(huì)打印輸出 hello、 、lambda。
Function這個(gè)函數(shù)式接口是用來接收一個(gè)對(duì)象并映射到另一個(gè)對(duì)象。
接收一個(gè)集合對(duì)象并返回另一個(gè)集合對(duì)象
public staticList map(List list, Function function) { List result = new ArrayList<>(); for (T t : list) { result.add(function.apply(t)); } return result; }
接收一個(gè)字符串集合并映射成其長度的整型集合后返回
// Lambda 實(shí)現(xiàn) Functionfunction = (String s) -> s.length(); // 匿名實(shí)現(xiàn) Function function1 = new Function () { @Override public Integer apply(String s) { return s.length(); } }; List integerList = map(stringList, function);
這樣 hello、 、lambda 就分別對(duì)應(yīng)其長度 5、0、6。
方法引用方法引用可以看作對(duì)特定 Lambda 的一種快捷寫法,本質(zhì)上依然是 Lambda。它的基本思想是,如果一個(gè) Lambda 代表的僅僅是 直接調(diào)用 這個(gè)方法而不是 描述如何去調(diào)用 這個(gè)方法,那最好還是用名稱來調(diào)用它。
例如在上一章的蘋果實(shí)例中我們需要用謂詞判斷是否成熟
// 普通 Lambda 寫法 PredicateapplePredicate1 = (Apple apple) -> apple.isAging(); // 方法引用寫法 Predicate applePredicate2 = Apple::isAging;
我們可以把方法引用看作對(duì) 僅僅涉及單一方法 的 Lambda 的語法糖。
構(gòu)造函數(shù)引用對(duì)于一個(gè)現(xiàn)有的構(gòu)造函數(shù),我們可以利用它的名稱和 new 來創(chuàng)建它的一個(gè)引用:ClassName::new。
// 普通 Lambda 創(chuàng)建對(duì)象 // SupplierappleSupplier = () -> new Apple(); // 構(gòu)造函數(shù)引用創(chuàng)建無參對(duì)象 Supplier appleSupplier = Apple::new; // 獲取實(shí)例 Apple apple1 = appleSupplier.get(); // 構(gòu)造函數(shù)引用創(chuàng)建有一個(gè)參數(shù)對(duì)象 Function appleFunction = Apple::new; // 獲取實(shí)例 Apple apple2 = appleFunction.apply("red"); // 構(gòu)造函數(shù)引用創(chuàng)建有兩個(gè)參數(shù)對(duì)象 BiFunction appleBiFunction = Apple::new; // 獲取實(shí)例 Apple apple3 = appleBiFunction.apply("red", 1);
那么當(dāng)參數(shù)有很多的時(shí)候怎么辦呢?我們可以自定義一個(gè)函數(shù)式接口進(jìn)行處理。
三個(gè)參數(shù)的構(gòu)造函數(shù)引用接口
public interface TriFunction{ R apply(T t, U u, V v); }
調(diào)用也是類似的
// 構(gòu)造函數(shù)引用創(chuàng)建有三個(gè)參數(shù)對(duì)象 TriFunctionLambda 和方法引用實(shí)戰(zhàn) 傳遞代碼appleTriFunction = Apple::new; // 獲取實(shí)例 Apple apple4 = appleTriFunction.apply("red", 1, true);
我們?nèi)绻獙?duì)一個(gè)蘋果集合按照重量從小到大排序,首先肯定要進(jìn)行判斷大小,然后對(duì)其進(jìn)行排序。按照我們已經(jīng)經(jīng)過一章多的學(xué)習(xí),應(yīng)該能很輕松地構(gòu)建一個(gè)解決方案。
1、創(chuàng)建比較器
public class AppleComparator implements Comparator{ @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } }
2、調(diào)用 List 已經(jīng)實(shí)現(xiàn)好的排序方法
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); // 現(xiàn)在 appleList 的順序是放入的順序 No.1、No.2 appleList.add(apple); // 依照重量排序后 appleList 的順序會(huì)變成 No.2、No.1 appleList.sort(new AppleComparator()); } }
這只是簡單的一個(gè)通過傳遞比較器來進(jìn)行排序。下面我們會(huì)用匿名類來實(shí)現(xiàn)上一章學(xué)習(xí)的 應(yīng)對(duì)不斷變化的需求。
使用匿名類到這一步其實(shí)已經(jīng)算得上符合正常軟件工程設(shè)計(jì)了,可以舍去 AppleComparator 這樣的實(shí)現(xiàn)方式。
appleList.sort(new Comparator使用 Lambda 表達(dá)式() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } });
緊接著我們可以更加高效地用 Lambda 實(shí)現(xiàn)。
appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));
我們還可以進(jìn)一步簡化代碼。Java 編譯器會(huì)從目標(biāo)類型自動(dòng)推斷出適合 Lambda 的返回類型。因此可以省略對(duì)傳入?yún)?shù)的類型定義。
// appleList 的類型定義是 List使用方法引用,傳遞進(jìn) sort() 的 Comparator 會(huì)自動(dòng)定義泛型為 Apple,所以 Lambda 也可以自動(dòng)推斷為 Comparator 的 compare() 傳入的類型。 appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
使用方法引用還可以更加讓人通俗易懂。
Comparator 有個(gè)靜態(tài)方法 comparing 可以接收 Function 函數(shù)式接口,用作比較依據(jù)。
// 傳入的 Function 本質(zhì)是將蘋果(Apple)映射成了蘋果的重量(Integer) // Functionfunction = (Apple o1) -> o1.getWeight(); // 方法引用后 Function function = Apple::getWeight; // 傳入到 comparing 靜態(tài)方法 Comparator.comparing(function);
所以最后可以簡化到極致
// 代表按照蘋果的重量進(jìn)行比較后排序 appleList.sort(Comparator.comparing(Apple::getWeight));復(fù)合 Lambda 表達(dá)式的有用方法
Java 8提供了允許進(jìn)行復(fù)合的方法。比如我們可以讓兩個(gè)謂詞之間做 or 操作,組成一個(gè)更加強(qiáng)大的謂詞。
比較器復(fù)合如果想要從大到小排序蘋果的重量(默認(rèn)的 sort() 是從小到大排序)
appleList.sort(Comparator.comparing(Apple::getWeight).reversed());
但是如果兩個(gè)的蘋果重量一樣,我們需要再根據(jù)顏色或者是否成熟來排序呢?
我們可以使用 thenComparing 方法
appleList.sort(Comparator // 從大到小排列蘋果的重量 .comparing(Apple::getWeight).reversed() // 然后按照顏色字母順序 .thenComparing(Apple::getColor) // 然后按照是否成熟 .thenComparing(Apple::getAging));
這樣就可以創(chuàng)建一個(gè)比較器鏈了。
謂詞復(fù)合謂詞接口包括三個(gè)方法:negate、and 和 or,我們可以以此創(chuàng)建復(fù)雜的謂詞。
選出蘋果不是紅的
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);
總結(jié)起來,除了 isRed 謂詞要在第一步寫,其余的地方都可以一句話寫完
Predicatepredicate = noRed .and(Apple::getAging) .or((Apple o1) -> o1.getWeight() > 1);
這樣從簡單的 Lambda 出發(fā),可以構(gòu)建更加復(fù)雜的表達(dá)式,但讀起來會(huì)更加輕松。注意,and 和 or 的是按照鏈中的位置執(zhí)行。
函數(shù)復(fù)合Function 接口包括兩個(gè)方法:andThen 和 compose,它們都會(huì)返回一個(gè) Function 的實(shí)例。
我們可以用 Function 定義三個(gè)函數(shù) 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 進(jìn)行運(yùn)算
// 結(jié)果為4 int result = h.apply(1);
compose()
// h(x) = g(f(x)) h = f.compose(g); // 結(jié)果為3 result = h.apply(1);
第三章的東西有點(diǎn)多,需要反復(fù)消化理解。
Java 8 實(shí)戰(zhàn) 第三章 Lambda 表達(dá)式 讀書筆記
歡迎加入咖啡館的春天(338147322)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70186.html
摘要:語言是強(qiáng)類型面向?qū)ο蟮恼Z言,所以必須提供一種數(shù)據(jù)類型作為表達(dá)式的返回值類型符合中函數(shù)格式的定義符合面向?qū)ο笠?guī)則,所以最終表達(dá)式要有一個(gè)映射成對(duì)象的過程。定一個(gè)函數(shù)式接口我們?cè)诮涌诶锒x了一個(gè)沒有參數(shù)返回值的抽象方法。 在JAVA中,Lambda 表達(dá)式(Lambda expression)是一個(gè)抽象方法的實(shí)現(xiàn)。這個(gè)抽象方法必須是在接口中聲明的,而且實(shí)現(xiàn)類只需要實(shí)現(xiàn)這一個(gè)抽象方法,我們稱...
摘要:表達(dá)式的主要作用就是代替匿名內(nèi)部類的煩瑣語法。從這點(diǎn)來看,表達(dá)式的代碼塊與匿名內(nèi)部類的方法體是相同的。與匿名內(nèi)部類相似的是,由于表達(dá)式訪問了局部變量,該局部變量相當(dāng)于與一個(gè)隱式的修飾,因此不允許對(duì)局部變量重新賦值。 函數(shù)式接口 函數(shù)式接口(Functional Interface)就是一個(gè)只有一個(gè)抽象方法(可以包含多個(gè)默認(rèn)方法或多個(gè)static方法)的普通接口,可以被隱式轉(zhuǎn)換為lamb...
摘要:在支持一類函數(shù)的語言中,表達(dá)式的類型將是函數(shù)。匿名函數(shù)的返回類型與該主體表達(dá)式一致如果表達(dá)式的主體包含一條以上語句,則表達(dá)式必須包含在花括號(hào)中形成代碼塊。注意,使用表達(dá)式的方法不止一種。 摘要:此篇文章主要介紹 Java8 Lambda 表達(dá)式產(chǎn)生的背景和用法,以及 Lambda 表達(dá)式與匿名類的不同等。本文系 OneAPM 工程師編譯整理。 Java 是一流的面向?qū)ο笳Z言,除了部分簡...
摘要:表達(dá)式簡介表達(dá)式是一個(gè)匿名函數(shù)對(duì)于而言并不很準(zhǔn)確,但這里我們不糾結(jié)這個(gè)問題。如果表達(dá)式的正文有一條以上的語句必須包含在大括號(hào)代碼塊中,且表達(dá)式的返回值類型要與匿名函數(shù)的返回類型相同。 版權(quán)聲明:本文由吳仙杰創(chuàng)作整理,轉(zhuǎn)載請(qǐng)注明出處:https://segmentfault.com/a/1190000009186509 1. 引言 在 Java 8 以前,若我們想要把某些功能傳遞給某些方...
摘要:初體驗(yàn)下面進(jìn)入本文的正題表達(dá)式。接下來展示表達(dá)式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構(gòu)造器引用進(jìn)一步簡化了表達(dá)式的書寫,但是個(gè)人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請(qǐng)聯(lián)系 tengfei@ifeve.com 本文主要記錄自己學(xué)習(xí)Java8的歷程,方便大家一起探討和自己的備忘。因?yàn)楸救艘彩莿倓傞_始學(xué)習(xí)Java8,所以文中肯定有錯(cuò)誤和理解偏...
摘要:函數(shù)式編程與面向?qū)ο缶幊瘫磉_(dá)式函數(shù)柯里化高階函數(shù)之劍什么是表達(dá)式例子定義表達(dá)式是一個(gè)匿名函數(shù),表達(dá)式基于數(shù)學(xué)中的演算得名,直接對(duì)應(yīng)于其中的抽象,是一個(gè)匿名函數(shù),即沒有函數(shù)名的函數(shù)。 函數(shù)式編程與面向?qū)ο缶幊蘙1]: Lambda表達(dá)式 函數(shù)柯里化 高階函數(shù).md 之劍 2016.5.2 11:19:09 什么是lambda表達(dá)式 例子 For example, in Lisp the...
閱讀 2632·2019-08-30 15:53
閱讀 2870·2019-08-29 16:20
閱讀 1081·2019-08-29 15:10
閱讀 1018·2019-08-26 10:58
閱讀 2188·2019-08-26 10:49
閱讀 630·2019-08-26 10:21
閱讀 700·2019-08-23 18:30
閱讀 1635·2019-08-23 15:58