和 Lambda 表達式 Say Hello
如果用一大段枯燥的文字去解釋一個我們并不熟悉的概念,我覺得和看天書并無區別。我之所以選擇學編程,就是因為沒有什么是寫段代碼不能搞定的。那么廢話少說,大家先看看清單一中的代碼。
清單一
public class Calculater {
public static void main(String[] args) {
final int a = 1, b = 2;
int result = add(new IIntegerMath() {
@Override
public int operation() {
return a + b;
}
});
System.out.println(result);
}
public static int add(IIntegerMath iIntegerMath) {
return iIntegerMath.operation();
}
}
interface IIntegerMath {
int operation();
}
看完清單一中的代碼,估計有人要說我了,因為這段代碼在上一篇博客中已經出現過了。但這又何妨,通過一段簡單的代碼,我們可以挖掘很多知識。在清單一代碼的 main 函數中,我調用了 add 函數,并使用了一個匿名內部類作為 add 函數的參數。匿名內部類被設計的目的之一就是,方便程序員將代碼作為數據傳遞。
現在問題來了,大家有沒有覺得這樣的代碼太過于冗余了。我們明明只需要 a + b 這一條語句,卻附加了很多其他的代碼(命令式代碼)。可能由于大家已經習慣了這樣的寫法,但 Java8 讓我們可以用更加簡單的代碼實現相同的功能。那么,我們一起來看清單二中用** Java8** 實現地與清單一功能相同的代碼。
清單二
public class Calculater {
public static void main(String[] args) {
int a = 1, b = 2;
int result = add(() -> a + b);
System.out.println(result);
}
public static int add(IIntegerMath iIntegerMath) {
return iIntegerMath.operation();
}
}
interface IIntegerMath {
int operation();
}
如果有人認為清單二中的代碼看著不爽,那么我建議他可以去泡個澡,然后剪個頭發。其實清單二中的代碼我已經在上篇博客中展示了,但我并沒有解釋 () -> a + b 是幾個意思。
我現在給大家分析下 () -> a + b ,其實這段代碼就是一個 Lambda 表達式,也可以理解為一個函數。-> 將參數和 Lambda 表達式的主體分割開了,-> 的左邊是參數所在的位置,() 表示無參數;-> 右邊的代碼是 Lambda 表達式的主體。
即使我們知道了 add 函數中那段代碼是什么意思,我認為有些人對于在 add 函數中直接傳入一個 Lambda 表達式還是難以理解。那么現在我用另一種方式重寫清單二中 main 函數中的代碼,請看清單三。
清單三
public static void main(String[] args) {
int a = 1, b = 2;
IIntegerMath integerMath = () -> a + b;
int result = add(integerMath);
System.out.println(result);
}
我相信清單三中的代碼對于大家來說都很熟悉。當你無法理解使用 Lambda 表達式作為函數參數的用法,你們就將 Lambda 表達式理解為一個對象的引用,雖然按理來說我們不能這么來理解。
Lambda 表達式的多種形式
不帶參數的 Lambda 表達式
清單四
public class LambdaLearn {
public static void main(String[] args) {
INoArguments noArguments =
() -> System.out.println("no argument");
}
}
interface INoArguments {
void printOperation();
}
清單四中展示了一個不帶參數的 Lambda 表達式,在 -> 的左邊使用空括號 () 代表沒有參數。
帶一個參數的Lambda表達式
清單五
public class LambdaLearn {
public static void main(String[] args) {
IOneArguments<Integer> oneArguments =
(a) -> a > 0;
}
}
interface IOneArguments<T> {
boolean assertOneNum(T argument);
}
清單五展示了一個只帶一個參數的 Lambda 表達式,因為只有一個參數,所以參數可以用括號包裹起來,也可以不用。
帶多個參數的 Lambda 表達式
清單六
public class LambdaLearn {
public static void main(String[] args) {
IMultiArguments<Integer> multiArguments =
(a, b) -> a + b;
}
}
interface IMultiArguments<T> {
T addOperation(T a, T b);
}
清單六中展示了一個帶多個參數的 Lambda 表達式。因為有多個參數,所以需要用括號將多個參數包裹起來。
注意:我不能用慣性思維去閱讀清單六中 Lambda 表達式。該 Lambda 表達式并不是將兩個數字相加,而是創建了一個函數,用來計算兩個數字相加的結果。變量 multiIArguments
的類型是 IMultiArguments<Integer>,它不是兩個數字相加的和,而是將兩個數字相加的那行代碼。
主體用被{}包裹的 Lambda 表達式
清單七
public class LambdaLearn {
public static void main(String[] args) {
INoArguments multiStatement = () -> {
System.out.println("this is the first code");
System.out.println("this is the second code");
};
INoArguments oneStatement = () -> {
System.out.println("only one code");
};
}
}
interface INoArguments {
void printOperation();
}
清單七中展示了,如果 Lambda 表達式的主體有多行代碼,那么就需要將多行代碼用**中括號 {} **包裹。其實當 Lambda 表達式的主體只有一行代碼的時候,大家可以根據自己的習慣決定是否使用中括號。
通過 Lambda 表達式來看 Java8
既成事實地final變量
在我們學 Java 基礎的時候,我們就知道匿名內部類只能引用外部的 final 變量。但我們卻發現,被 Lambda 表達式引用的外部變量并沒有被 final 修飾。如清單八中的代碼所示,被 Lambda 表達式引用的外部變量 a 和 b 并誒有被 final 修飾,這是因為 Java8 為我們省去了一些操作,這樣代碼看上去會更加干凈舒服。雖然 a 和 b 沒有被顯示地被 final 修飾,但它們依然是事實上的 final 變量。你們可以根據自己的喜好,選擇性地給被 Lambda 表達式引用的外部變量加上 final 修飾符。
清單八
public static void main(String[] args) {
int a = 1, b = 2;
IIntegerMath integerMath = () -> a + b;
int result = add(integerMath);
System.out.println(result);
}
類型推斷
不知大家是否有注意,本文中使用的 Lambda 表達式都沒有為參數指明類型,這是因為 Java8 引入了比 Java7 更加強大的目標類型推斷。如清單九中的代碼所示, Lambda 表達式的參數 x 并沒有被指明類型,但 javac 會根據變量 atLeast 的類型 Predicate<Integer> 推斷出目標類型。在日常的開發中,請大家根據具體情況選擇是否給 Lambda 表達式的參數顯示地指明類型。
清單九
Predicate<Integer> atLeast = x -> x > 5;
interface Predicate<Integer> {
boolean test(T t);
}
彩蛋
其實我的微信里有關注很多技術公眾號,但真正喜歡并經常閱讀的卻寥寥無幾,其中劉欣大神的碼農翻身就是我非常喜歡的一個公眾號。劉欣大哥是一個有 15 年工作經驗的前 IBM 架構師,他是一個熱愛編程的資深碼農,他用心去寫好每一篇博客,他的每篇博客都是一個故事,他用一個個精彩短小的故事解釋有點枯燥的技術。下面是他公眾號的二維碼,請關注他,你們會收獲很多。
