第一次是接觸Lambda表達(dá)式是在TypeScript中(JavaScript的超集中),當(dāng)時(shí)是為了讓TypeScript的this方法外而不是本方法內(nèi)所使用的。使用過后突然想到Lambda不是JDK8的重量級(jí)新特性么?于是感覺查閱相關(guān)資料并記錄下來:
一. 行為參數(shù)化
行為參數(shù)化簡(jiǎn)單的說就是函數(shù)的主體僅包含模板類通用代碼,而一些會(huì)隨著業(yè)務(wù)場(chǎng)景而變化的邏輯則以參數(shù)的形式傳遞到函數(shù)之中,采用行為參數(shù)化可以讓程序更加的通用,以應(yīng)對(duì)頻繁變更的需求。
考慮一個(gè)業(yè)務(wù)場(chǎng)景,假設(shè)我們需要通過程序?qū)μO果進(jìn)行篩選,我們先定義一個(gè)蘋果的實(shí)體:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Apple { /** 編號(hào) */ 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 } |
用戶最開始的需求可能只是簡(jiǎn)單的希望能夠通過程序篩選出綠色的蘋果,于是我們可以很快的通過程序?qū)崿F(xiàn):
1
2
3
4
5
6
7
8
9
|
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; } |
這段代碼很簡(jiǎn)單,沒有什么值得說的。但當(dāng)如果用戶需求變?yōu)榫G色,看起來修改代碼也很簡(jiǎn)單,無非是把判斷條件的綠色改為紅色而已。但我們需要考慮另外一個(gè)問題,如果變化條件頻繁的改變這么辦?如果只是顏色的改變,那好我們直接讓用戶把顏色的判斷條件傳遞進(jìn)來,判斷方法的參數(shù)變”要判斷的集合以及要篩選的顏色”。但如果用戶不僅僅是判斷顏色,還想判斷重量呀,大小呀什么的,怎么辦?你是不是覺得我們依次添加不同的參數(shù)來完成判斷就可以了?但這樣通過傳遞參數(shù)的方式真的好嗎?如果篩選條件越來越多,組合模式越來越復(fù)雜,我們是不是需要考慮到所有的情況,并針對(duì)每一種情況都有相應(yīng)的應(yīng)對(duì)策略呢?這個(gè)時(shí)候我們就可以將行為參數(shù)化,篩選條件抽離出來當(dāng)做參數(shù)傳遞進(jìn)來,此時(shí)我們可以封裝一個(gè)判斷的接口出來:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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ù)傳遞到方法中,此時(shí)采用匿名內(nèi)部類的方法:
1
2
3
4
5
6
7
8
9
10
11
|
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è)計(jì)在jdk內(nèi)部也經(jīng)常采用,比如Java.util.Comparator,java.util.concurrent.Callable等,使用這一類接口的時(shí)候,我們都可以在具體調(diào)用的地方用過匿名類來指定函數(shù)的具體執(zhí)行邏輯,不過從上面的代碼塊來看,雖然很極客,但是不夠簡(jiǎn)潔,在java8中我們可以通過lambda來簡(jiǎn)化:
1
2
3
4
|
// 篩選蘋果 List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100 ); //()->xxx ()里面就是方法參數(shù),xxx是方法實(shí)現(xiàn) |
二. lambda表達(dá)式定義
我們可以將lambda表達(dá)式定義為一種 簡(jiǎn)潔、可傳遞的匿名函數(shù),首先我們需要明確lambda表達(dá)式本質(zhì)上是一個(gè)函數(shù),雖然它不屬于某個(gè)特定的類,但具備參數(shù)列表、函數(shù)主體、返回類型,以及能夠拋出異常;其次它是匿名的,lambda表達(dá)式?jīng)]有具體的函數(shù)名稱;lambda表達(dá)式可以像參數(shù)一樣進(jìn)行傳遞,從而極大的簡(jiǎn)化代碼的編寫。格式定義如下:
格式一: 參數(shù)列表 -> 表達(dá)式
格式二: 參數(shù)列表 -> {表達(dá)式集合}
需要注意的是,lambda表達(dá)式隱含了return關(guān)鍵字,所以在單個(gè)的表達(dá)式中,我們無需顯式的寫return關(guān)鍵字,但是當(dāng)表達(dá)式是一個(gè)語句集合的時(shí)候,則需要顯式添加return,并用花括號(hào){ }將多個(gè)表達(dá)式包圍起來,下面看幾個(gè)例子:
1
2
3
4
5
6
7
8
9
|
//返回給定字符串的長(zhǎng)度,隱含return語句 (String s) -> s.length() // 始終返回42的無參方法 () -> 42 // 包含多行表達(dá)式,則用花括號(hào)括起來 ( int x, int y) -> { int z = x * y; return x + z; } |
三. 依托于函數(shù)式接口使用lambda表達(dá)式
lambda表達(dá)式的使用需要借助于函數(shù)式接口,也就是說只有函數(shù)式接口出現(xiàn)地方,我們才可以將其用lambda表達(dá)式進(jìn)行簡(jiǎn)化。
自定義函數(shù)式接口:
函數(shù)式接口定義為只具備 一個(gè)抽象方法 的接口。java8在接口定義上的改進(jìn)就是引入了默認(rèn)方法,使得我們可以在接口中對(duì)方法提供默認(rèn)的實(shí)現(xiàn),但是不管存在多少個(gè)默認(rèn)方法,只要具備一個(gè)且只有一個(gè)抽象方法,那么它就是函數(shù)式接口,如下(引用上面的AppleFilter):
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * 蘋果過濾接口 */ @FunctionalInterface public interface AppleFilter { /** * 篩選條件抽象 * * @param apple * @return */ boolean accept(Apple apple); } |
AppleFilter僅包含一個(gè)抽象方法 accept(Apple apple),依照定義可以將其視為一個(gè)函數(shù)式接口,在定義時(shí)我們?yōu)樵摻涌谔砑恿薂FunctionalInterface注解,用于標(biāo)記該接口是函數(shù)式接口,不過這個(gè)接口是可選的,當(dāng)添加了該接口之后,編譯器就限制了該接口只允許有一個(gè)抽象方法,否則報(bào)錯(cuò),所以推薦為函數(shù)式接口添加該注解。
jdk自帶的函數(shù)式接口:
jdk為lambda表達(dá)式已經(jīng)內(nèi)置了豐富的函數(shù)式接口,下面分別就Predicate<T>、Consumer<T>、Function<T, R>的使用示例說明。
Predicate:
1
2
3
4
5
6
7
8
9
10
11
|
@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,利用我們?cè)谕獠吭O(shè)定的條件對(duì)于傳入的參數(shù)進(jìn)行校驗(yàn),并返回驗(yàn)證結(jié)果boolean,下面利用Predicate對(duì)List集合的元素進(jìn)行過濾:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/** * * @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; } |
使用:
1
|
demo.filter(list, (String str) -> null != str && !str.isEmpty()); |
Consumer
1
2
3
4
5
6
7
8
9
10
|
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); } |
Consumer提供了一個(gè)accept抽象函數(shù),該函數(shù)接收參數(shù),但不返回值,下面利用Consumer遍歷集合.
1
2
3
4
5
6
7
8
9
10
11
12
|
/** * 遍歷集合,執(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ù)式接口,遍歷字符串集合,并打印非空字符串:
1
2
3
4
5
|
demo.filter(list, (String str) -> { if (StringUtils.isNotBlank(str)) { System.out.println(str); } }); |
Function
1
2
3
4
5
6
7
8
9
10
|
@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對(duì)集合進(jìn)行轉(zhuǎn)換:
1
2
3
4
5
6
7
|
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; } |
其他:
1
|
demo.filter(list, (String str) -> Integer.parseInt(str)); |
上面這些函數(shù)式接口還提供了一些邏輯操作的默認(rèn)實(shí)現(xiàn),留到后面介紹java8接口的默認(rèn)方法時(shí)再講吧~
使用過程中需要注意的一些事情:
類型推斷:
在編碼過程中,有時(shí)候可能會(huì)疑惑我們的調(diào)用代碼會(huì)去具體匹配哪個(gè)函數(shù)式接口,實(shí)際上編譯器會(huì)根據(jù)參數(shù)、返回類型、異常類型(如果存在)等做正確的判定。
在具體調(diào)用時(shí),在一些時(shí)候可以省略參數(shù)的類型,從而進(jìn)一步簡(jiǎn)化代碼:
1
2
3
4
5
6
7
|
// 篩選蘋果 List<Apple> filterApples = filterApplesByAppleFilter(apples, (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100 ); // 某些情況下我們甚至可以省略參數(shù)類型,編譯器會(huì)根據(jù)上下文正確判斷 List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.R ED.equals(apple.getColor()) && apple.getWeight() >= 100 ); |
局部變量
上面所有例子我們的lambda表達(dá)式都是使用其主體參數(shù),我們也可以在lambda中使用局部變量,如下
1
2
3
|
int weight = 100 ; List<Apple> filterApples = filterApplesByAppleFilter(apples, apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);: |
該例子中我們?cè)趌ambda中使用了局部變量weight,不過在lambda中使用局部變量必須要求該變量 顯式聲明為final或事實(shí)上的final ,這主要是因?yàn)榫植孔兞看鎯?chǔ)在棧上,lambda表達(dá)式則在另一個(gè)線程中運(yùn)行,當(dāng)該線程視圖訪問該局部變量的時(shí)候,該變量存在被更改或回收的可能性,所以用final修飾之后就不會(huì)存在線程安全的問題。
四. 方法引用
采用方法引用可以更近一步的簡(jiǎn)化代碼,有時(shí)候這種簡(jiǎn)化讓代碼看上去更加的直觀,先看一個(gè)例子:
1
2
3
4
5
|
/* ... 省略apples的初始化操作 */ // 采用lambda表達(dá)式 apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight())); // 采用方法引用 apples.sort(Comparator.comparing(Apple::getWeight)); |
方法引用通過::將方法隸屬和方法自身連接起來,主要分為三類:
靜態(tài)方法
1
|
(args) -> ClassName.staticMethod(args) |
轉(zhuǎn)換成
1
|
ClassName::staticMethod |
參數(shù)的實(shí)例方法
1
|
(args) -> args.instanceMethod() |
轉(zhuǎn)換成
1
|
ClassName::instanceMethod // ClassName是args的類型 |
外部的實(shí)例方法
1
|
(args) -> ext.instanceMethod(args) |
轉(zhuǎn)換成
1
|
ext::instanceMethod(args) |
參考:
http://www.codeceo.com/article/lambda-of-java-8.html
以上所述是小編給大家介紹的JDK8新特性之Lambda表達(dá)式,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)服務(wù)器之家網(wǎng)站的支持!
原文鏈接:http://blog.csdn.net/canot/article/details/52951828