stream and lambda(1) - lambda 簡介
從 java8
開始, lambda
正式在 JAVA
中出現。有人說它類似於其他函數語言程式設計語言中的閉包,有人說它是一種語法糖。讓我們來一步一步瞭解它。
lambda 表示式的結構
lambda 表示式的結構大致如下,同時遵循偷懶原則:能省則省,能不寫就不寫。
(型別)(引數1, 引數2, …) -> {方法實現}
- 型別一般情況下可以自動推匯出來,此時可以省略不寫。如果存在多個型別滿足條件,則需指定型別。
- 可以有零個或多個引數,引數寫在圓括號內。
-
沒有引數時只需寫
()
,只有一個引數且型別可以推導時可以不寫圓括號。 -
引數的型別可以省略不寫,根據上下文來推斷。例如:
(int a)->{}
與(a)->{}
與a->{}
效果相同。 - 方法實現可以有零條或者多條語句,方法實現寫在花括號內。
-
如果方法實現只有一條語句,則花括號可以省略。如:
a -> return a>1
。 -
如果方法實現只有一條語句,
return
關鍵字可以省略。如:a -> a==1
。 - 如果方法實現有多條語句,則需寫在花括號內。
-
如果方法實現只有一條語句,且那一條的內容是某個方法,且方法引數是入參,可簡寫。如:
(int a) -> {return String.valueOf(a)}
等效於a -> String.valueOf(a)
,也等效於String::valueOf
。
從策略模式開始演化
先給定場景:A市有一批人提交了落戶申請,但是A市不像某經濟特區,來了還不是A市人,要滿足一定的條件才可以落戶。
//定義居民物件,包含姓名、年齡、社保月數、認證證書級別 public class Citizen { private String name; private int age; private int socialInsuranceMonth; private int certificateLevel; }
提供初始化資料方法
List<Citizen> initCitizens() { List<Citizen> citizens = new ArrayList<>(); citizens.add(new Citizen("張三", 23, 11, 0)); citizens.add(new Citizen("李四", 24, 13, 0)); citizens.add(new Citizen("王五", 25, 15, 2)); citizens.add(new Citizen("趙六", 26, 8, 3)); citizens.add(new Citizen("劉七", 35, 12, 3)); citizens.add(new Citizen("宋八", 41, 0, 2)); return citizens; }
定義資料處理框架方法及處理介面
List<Citizen> filterApply(List<Citizen> citizenList, SettleStrategy strategy) { List<Citizen> resList = new ArrayList<>(); for (Citizen citizen : citizenList) { if (strategy.test(citizen)) { resList.add(citizen); } } return resList; } public interface SettleStrategy { boolean test(Citizen citizen); }
策略模式
假設落戶有兩種方式:
- 不超過40歲且有國家認證的高於2級的證書
- 不超過40歲且累計交滿12個月社保
定義策略實現類
public class CertificateSettleStrategy implements SettleStrategy { @Override public boolean test(Citizen citizen) { return citizen.getAge() <=40 && citizen.getCertificateLevel() > 2; } } public class SocialInsuranceSettleStrategy implements SettleStrategy { @Override public boolean test(Citizen citizen) { return citizen.getAge() <=40 && citizen.getSocialInsuranceMonth() >= 12; } }
則使用策略模式的寫法大致如下:
public void testStrategy() { //證書達標的 List<Citizen> citizens = filterApply(initCitizens(), new CertificateSettleStrategy()); System.out.println(citizens); //社保達標的 citizens = filterApply(initCitizens(), new SocialInsuranceSettleStrategy()); System.out.println(citizens); }
點評:
從上述程式碼可以看出,使用策略模式時,要求必須有指定的策略類。
雖然好處是有新策略可以直接新增一個策略處理類,無需反覆修改處理邏輯。
但是仍然面臨著策略變化,必須增加新策略類的問題。
匿名內部類
使用匿名內部類,可無需再定義策略處理類。可能你在某些業務地方用過,比如往執行緒池裡丟一個 Runnable
,你可能不會再單獨定義一個子類。
public void testAnonymous() { List<Citizen> citizens = filterApply(initCitizens(), new SettleStrategy() { @Override public boolean test(Citizen citizen) { return citizen.getAge() <=40 && citizen.getSocialInsuranceMonth() >= 12; } }); System.out.println(citizens); }
點評:
從上述程式碼可以看出,使用匿名內部類時,可以允許自己不再定義單獨的類,但還需顯式的 new
子類並 override
。
相比策略模式,程式碼更集中,也不必在專案裡維護太多的類。缺點則是當處理邏輯較複雜時,寫法不優雅,看起來會雜亂。
此時,比如在 intellij idea
裡,應該會提示你程式碼可以 replace with lambda
了
lambda 表示式
lambda 的寫法會簡潔不少,遵循上面說的偷懶原則,省略型別、省略引數、省略括號。
public void testLambda() { List<Citizen> citizens = filterApply(initCitizens(), citizen -> citizen.getAge() <=40 && citizen.getSocialInsuranceMonth() >= 12); System.out.println(citizens); }
點評:
相比匿名內部類,lambda 更近一步。此時,不用再出現 SettleStrategy
字樣,不必再出現 test
方法字樣。
既然如此,那麼,此時就剩最後一個問題了:
為什麼還要定義 SettleStrategy
介面呢?
stream
stream 提供了一系列的便捷處理方法,java8 也提供了一系列的通用的函式式介面,讓我們免去定義一些不必要的介面類。當然,stream 與函式式介面不在本篇的內容範圍內。
通過 stream 的寫法,可以解決 lambda 遺留的那個問題。
public void testStream() { Citizen first = initCitizens().stream() .filter(citizen -> citizen.getAge() < 35) .findFirst() .orElse(null); System.out.println(first); }
看,鏈式呼叫,是不是很簡潔易讀?
lambda 表示式與匿名內部類的區別
從上面的介紹可以看出,其實 lambda 表示式和匿名內部類很相似,相似到只是寫法稍作改變。如果從上面所說的偷懶原則來看,lambda 表示式就是匿名實現了函式式介面,並把匿名內部類一步步的偷懶簡化。但是二者從本質上來講,又是不同的。
匿名內部類,本質還是一個類,例項化的是一個物件;lambda 表示式從根本上來說,是一種編譯器會特定處理的語法。
從使用上來看,匿名內部類還是要 new 出來一個例項化物件的,而 this 關鍵字在匿名內部中使用時,指的是內部類,而非外部類;對於 lambda 表示式,this 關鍵字則代表的是外部類(lambda所在的類)。從編譯輸出來看,當使用匿名內部類時,會編譯出一個單獨的內部類的 class;而 lambda 表示式則不會編譯出單獨的 class 出來。
總結
本文總結了 lambda 表示式的結構,同時從策略模式說起,一步步的簡化,逐步引出了 lambda 表示式,並比較不同寫法之間的差異性,同時稍稍介紹了 stream。
lambda 表示式可以讓我們不用去費力的給一個方法、或者一個類命名,讓我們可以以匿名的方式去使用它。但是如果匿名方法裡的處理邏輯太複雜,則不建議使用 lambda 表示式去寫,lambda 所表達的內容應該是簡單易讀的。
注:本文配套程式碼可在 github
檢視: stream-and-lambda
- stream and lambda(18) - 終止操作之 stream 收集器 collectors
- stream and lambda(15) - 終止操作之 stream 陣列操作 toArray
- stream and lambda(13) - 終止操作之計數 count 與比較 min、max
- stream and lambda(12) - 終止操作之查詢與匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)
- stream and lambda(11) - Optional 簡介
- stream and lambda(10) - 中間操作之排序sorted與除錯peek
- stream and lambda(9) - 中間操作之map操作(map、flatmap)
- stream and lambda(8) - 中間操作之篩選操作(filter、distinct、limit、skip)
- stream and lambda(7) - stream 的建立
- stream and lambda(6) - stream 簡介
- stream and lambda(5) - lambda 表示式最佳實踐
- stream and lambda(3) - jdk 提供的函式式介面
- stream and lambda(2) - 函式式介面簡介
- stream and lambda(1) - lambda 簡介