深入淺析JDK8新特性之Lambda表達式
第一次是接觸Lambda表達式是在TypeScript中(JavaScript的超集中),當(dāng)時是為了讓TypeScript的this方法外而不是本方法內(nèi)所使用的。使用過后突然想到Lambda不是JDK8的重量級新特性么?于是感覺查閱相關(guān)資料并記錄下來:
一. 行為參數(shù)化
行為參數(shù)化簡單的說就是函數(shù)的主體僅包含模板類通用代碼,而一些會隨著業(yè)務(wù)場景而變化的邏輯則以參數(shù)的形式傳遞到函數(shù)之中,采用行為參數(shù)化可以讓程序更加的通用,以應(yīng)對頻繁變更的需求。
考慮一個業(yè)務(wù)場景,假設(shè)我們需要通過程序?qū)μO果進行篩選,我們先定義一個蘋果的實體:
public class Apple { /** 編號 */ private long id; /** 顏色 */ private Color color; /** 重量 */ private float weight; /** 產(chǎn)地 */ private String origin; public Apple() { } public Apple(long id, Color color, float weight, String origin) { this.id = id; this.color = color; this.weight = weight; this.origin = origin; } // 省略getter和setter }
用戶最開始的需求可能只是簡單的希望能夠通過程序篩選出綠色的蘋果,于是我們可以很快的通過程序?qū)崿F(xiàn):
public static List<Apple> filterGreenApples(List<Apple> apples) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (Color.GREEN.equals(apple.getColor())) { filterApples.add(apple); } } return filterApples; }
這段代碼很簡單,沒有什么值得說的。但當(dāng)如果用戶需求變?yōu)榫G色,看起來修改代碼也很簡單,無非是把判斷條件的綠色改為紅色而已。但我們需要考慮另外一個問題,如果變化條件頻繁的改變這么辦?如果只是顏色的改變,那好我們直接讓用戶把顏色的判斷條件傳遞進來,判斷方法的參數(shù)變”要判斷的集合以及要篩選的顏色”。但如果用戶不僅僅是判斷顏色,還想判斷重量呀,大小呀什么的,怎么辦?你是不是覺得我們依次添加不同的參數(shù)來完成判斷就可以了?但這樣通過傳遞參數(shù)的方式真的好嗎?如果篩選條件越來越多,組合模式越來越復(fù)雜,我們是不是需要考慮到所有的情況,并針對每一種情況都有相應(yīng)的應(yīng)對策略呢?這個時候我們就可以將行為參數(shù)化,篩選條件抽離出來當(dāng)做參數(shù)傳遞進來,此時我們可以封裝一個判斷的接口出來:
public interface AppleFilter { /** * 篩選條件抽象 * * @param apple * @return */ boolean accept(Apple apple); } /** * 將篩選條件封裝成接口 * * @param apples * @param filter * @return */ public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) { List<Apple> filterApples = new ArrayList<>(); for (final Apple apple : apples) { if (filter.accept(apple)) { filterApples.add(apple); } } return filterApples; }
通過上面行為抽象化之后,我們可以在具體調(diào)用的地方設(shè)置篩選條件,并將條件作為參數(shù)傳遞到方法中,此時采用匿名內(nèi)部類的方法:
public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); // 篩選蘋果 List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() { @Override public boolean accept(Apple apple) { // 篩選重量大于100g的紅蘋果 return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100; } }); }
這樣的設(shè)計在jdk內(nèi)部也經(jīng)常采用,比如Java.util.Comparator,java.util.concurrent.Callable等,使用這一類接口的時候,我們都可以在具體調(diào)用的地方用過匿名類來指定函數(shù)的具體執(zhí)行邏輯,不過從上面的代碼塊來看,雖然很極客,但是不夠簡潔,在java8中我們可以通過lambda來簡化:
// 篩選蘋果 List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); //()->xxx ()里面就是方法參數(shù),xxx是方法實現(xiàn)
二. lambda表達式定義
我們可以將lambda表達式定義為一種 簡潔、可傳遞的匿名函數(shù),首先我們需要明確lambda表達式本質(zhì)上是一個函數(shù),雖然它不屬于某個特定的類,但具備參數(shù)列表、函數(shù)主體、返回類型,以及能夠拋出異常;其次它是匿名的,lambda表達式?jīng)]有具體的函數(shù)名稱;lambda表達式可以像參數(shù)一樣進行傳遞,從而極大的簡化代碼的編寫。格式定義如下:
格式一: 參數(shù)列表 -> 表達式
格式二: 參數(shù)列表 -> {表達式集合}
需要注意的是,lambda表達式隱含了return關(guān)鍵字,所以在單個的表達式中,我們無需顯式的寫return關(guān)鍵字,但是當(dāng)表達式是一個語句集合的時候,則需要顯式添加return,并用花括號{ }將多個表達式包圍起來,下面看幾個例子:
//返回給定字符串的長度,隱含return語句 (String s) -> s.length() // 始終返回42的無參方法 () -> 42 // 包含多行表達式,則用花括號括起來 (int x, int y) -> { int z = x * y; return x + z; }
三. 依托于函數(shù)式接口使用lambda表達式
lambda表達式的使用需要借助于函數(shù)式接口,也就是說只有函數(shù)式接口出現(xiàn)地方,我們才可以將其用lambda表達式進行簡化。
自定義函數(shù)式接口:
函數(shù)式接口定義為只具備 一個抽象方法 的接口。java8在接口定義上的改進就是引入了默認方法,使得我們可以在接口中對方法提供默認的實現(xiàn),但是不管存在多少個默認方法,只要具備一個且只有一個抽象方法,那么它就是函數(shù)式接口,如下(引用上面的AppleFilter):
/** * 蘋果過濾接口 */ @FunctionalInterface public interface AppleFilter { /** * 篩選條件抽象 * * @param apple * @return */ boolean accept(Apple apple); }
AppleFilter僅包含一個抽象方法 accept(Apple apple),依照定義可以將其視為一個函數(shù)式接口,在定義時我們?yōu)樵摻涌谔砑恿薂FunctionalInterface注解,用于標(biāo)記該接口是函數(shù)式接口,不過這個接口是可選的,當(dāng)添加了該接口之后,編譯器就限制了該接口只允許有一個抽象方法,否則報錯,所以推薦為函數(shù)式接口添加該注解。
jdk自帶的函數(shù)式接口:
jdk為lambda表達式已經(jīng)內(nèi)置了豐富的函數(shù)式接口,下面分別就Predicate<T>、Consumer<T>、Function<T, R>的使用示例說明。
Predicate:
@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的功能類似于上面的AppleFilter,利用我們在外部設(shè)定的條件對于傳入的參數(shù)進行校驗,并返回驗證結(jié)果boolean,下面利用Predicate對List集合的元素進行過濾:
/** * * @param list * @param predicate * @param <T> * @return */ public <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> newList = new ArrayList<T>(); for (final T t : list) { if (predicate.test(t)) { newList.add(t); } } return newList; }
使用:
demo.filter(list, (String str) -> null != str && !str.isEmpty());
Consumer
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); }
Consumer提供了一個accept抽象函數(shù),該函數(shù)接收參數(shù),但不返回值,下面利用Consumer遍歷集合.
/** * 遍歷集合,執(zhí)行自定義行為 * * @param list * @param consumer * @param <T> */ public <T> void filter(List<T> list, Consumer<T> consumer) { for (final T t : list) { consumer.accept(t); } }
利用上面的函數(shù)式接口,遍歷字符串集合,并打印非空字符串:
demo.filter(list, (String str) -> { if (StringUtils.isNotBlank(str)) { System.out.println(str); } });
Function
@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); }
Funcation執(zhí)行轉(zhuǎn)換操作,輸入是類型T的數(shù)據(jù),返回R類型的數(shù)據(jù),下面利用Function對集合進行轉(zhuǎn)換:
public <T, R> List<R> filter(List<T> list, Function<T, R> function) { List<R> newList = new ArrayList<R>(); for (final T t : list) { newList.add(function.apply(t)); } return newList; }
其他:
demo.filter(list, (String str) -> Integer.parseInt(str));
上面這些函數(shù)式接口還提供了一些邏輯操作的默認實現(xiàn),留到后面介紹java8接口的默認方法時再講吧~
使用過程中需要注意的一些事情:
類型推斷:
在編碼過程中,有時候可能會疑惑我們的調(diào)用代碼會去具體匹配哪個函數(shù)式接口,實際上編譯器會根據(jù)參數(shù)、返回類型、異常類型(如果存在)等做正確的判定。
在具體調(diào)用時,在一些時候可以省略參數(shù)的類型,從而進一步簡化代碼:
// 篩選蘋果 List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100); // 某些情況下我們甚至可以省略參數(shù)類型,編譯器會根據(jù)上下文正確判斷 List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.R ED.equals(apple.getColor()) && apple.getWeight() >= 100);
局部變量
上面所有例子我們的lambda表達式都是使用其主體參數(shù),我們也可以在lambda中使用局部變量,如下
int weight = 100; List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);:
該例子中我們在lambda中使用了局部變量weight,不過在lambda中使用局部變量必須要求該變量 顯式聲明為final或事實上的final ,這主要是因為局部變量存儲在棧上,lambda表達式則在另一個線程中運行,當(dāng)該線程視圖訪問該局部變量的時候,該變量存在被更改或回收的可能性,所以用final修飾之后就不會存在線程安全的問題。
四. 方法引用
采用方法引用可以更近一步的簡化代碼,有時候這種簡化讓代碼看上去更加的直觀,先看一個例子:
/* ... 省略apples的初始化操作 */ // 采用lambda表達式 apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight())); // 采用方法引用 apples.sort(Comparator.comparing(Apple::getWeight));
方法引用通過::將方法隸屬和方法自身連接起來,主要分為三類:
靜態(tài)方法
(args) -> ClassName.staticMethod(args)
轉(zhuǎn)換成
ClassName::staticMethod
參數(shù)的實例方法
(args) -> args.instanceMethod()
轉(zhuǎn)換成
ClassName::instanceMethod // ClassName是args的類型
外部的實例方法
(args) -> ext.instanceMethod(args)
轉(zhuǎn)換成
ext::instanceMethod(args)
參考:
http://www.codeceo.com/article/lambda-of-java-8.html
以上所述是小編給大家介紹的JDK8新特性之Lambda表達式,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Struts2學(xué)習(xí)教程之Action類如何訪問WEB資源
這篇文章主要給大家介紹了關(guān)于Struts2學(xué)習(xí)教程之Action類如何訪問WEB資源的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04mybatis實現(xiàn)獲取入?yún)⑹荓ist和Map的取值
這篇文章主要介紹了mybatis實現(xiàn)獲取入?yún)⑹荓ist和Map的取值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06java實現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例
這篇文章主要介紹了java實現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例,需要的朋友可以參考下2014-03-03Spring注解驅(qū)動之@EventListener注解使用方式
這篇文章主要介紹了Spring注解驅(qū)動之@EventListener注解使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09Springboot項目通過redis實現(xiàn)接口的冪等性
這篇文章主要為大家介紹了Springboot項目通過redis實現(xiàn)接口的冪等性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12SpringBoot@DeleteMapping(/xxx/{id})請求報405的解決
這篇文章主要介紹了SpringBoot@DeleteMapping(/xxx/{id})請求報405的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01IDEA遠程連接HBase及其Java API實戰(zhàn)詳解
這篇文章主要介紹了IDEA遠程連接HBase及其Java API實戰(zhàn)詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04