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