我們都知道Java 8 支援Lambda表示式,但是平時開發中也很難用到這個東東,但是作為專業的程式設計師,技多不壓身(其實我是在學Kotlin中,發現裡面大量的運用到了Lambda表示式,看的我一臉懵逼,所以只好來學習學習,不然怎麼出去裝逼,怎麼騷浪賤)。好,收,讓我們來看看Lambda的前世今生。
一、Lambda到底是什麼鬼?
說到Lambda,不得不說到函數語言程式設計,說到函數語言程式設計不得不說到λ演算,λ演算是數學家Church提出的。λ演算中最關鍵的要素就是函式被當作變數處理能夠參與運算。(看到這裡我社會大佬,才是這些數學家好嘛,打call,打call)。
繼續繼續,我們來看看Lambda的官方定義。
A lambda expression is like a method: it provides a list of formal parameters and a body - an expression or block - expressed in terms of those parameters. 翻譯如下:Lambda表示式類似於一種方法:它提供了一個形參列表和一個表示式或用這些形參的程式碼塊。
我去。反正官方的解釋我是一點都沒有看懂。個人覺得Lambda表示式更像是“語法糖”, 不僅使程式碼變的更加簡潔,還是程式碼更有閱讀性。說了這麼多,無碼無真相。讓我們來探討探討Lambda表示式。開車開車,請上車~~~
1.1 Lambda表示式分析
那在Java中如果我們要給一個變數賦值,我們一般的操作如下:
int a = 1;
String b = "abc";
boolean c = true;
複製程式碼
但是如果我們根據λ演算要素,將函式當做變數處理,將函式賦值給一個變數。
addMethod = public void add(int a,int b){
return a+b;
}
複製程式碼
哇,這程式碼真是蛇皮,這與我們實際編碼中書寫的Lambda表示式程式碼完全不一樣好嘛,對啊!作為一個程式設計師,我們萬事都要講究一個優雅好嘛,不優雅,你就是要我命,你造否?所以為了程式碼簡潔與優雅。Sun公司的大佬們移除了一些不必要的宣告。我們一步一步的來分析。
1.1.1 去除函式修飾符
addMethod = void add(int a,int b){
return a+b;
}
複製程式碼
上面操作,我們發現了我們移除了public 修飾符,設想,如果我們需要將一個函式賦值給一個變數,那麼起訪問許可權控制的,肯定是變數的修飾符,所以說函式的修飾符可以移除。我們繼續往下走。
1.1.2 去除函式名稱
addMethod = void (int a,int b){
return a+b;
}
複製程式碼
去除函式名稱,主要的原因是,既然我已將函式賦值給變數了。那所有的操作都是與這個變數相關,所以函式的名稱是可以不要的。對實際的操作沒有影響。
1.1.3 去除函式返回型別
addMethod = (int a,int b){
return a+b;
}
複製程式碼
為什麼會去掉函式返回型別呢?函式內部到底有沒有返回值,你覺得編譯器心裡難道沒有一點B數嘛,所以我們完全可以省略這個返回型別。
1.1.4 去除引數型別
addMethod = (a, b){
return a+b;
}
複製程式碼
去掉引數型別,同1.1.2原因,編譯器知道傳入的引數型別。所以可以省略。
1.1.5 引數與函式體區分
addMethod = (a, b)->{
return a+b;
}
複製程式碼
為了更好的將引數與函式體分開。Java中的語法是使用**"->"**來區分,這些程式碼看起來更簡潔。
1.2 Lambda表示式對應變數型別
到現在為止,我們已經成功的將一個函式賦值給一個變數。這個時候我們需要理解的是這個變數的具體型別是什麼,我們都知道我們宣告變數的時候,前方都會宣告其型別。那這個變數的型別到底是什麼呢?
在Java8中,Lambda表示式所對應的型別都是介面,Lambda所表達的就是那個介面對應函式的具體程式碼。可能大家這裡不是很清楚。概念還是很模糊。我們看一下下面的例子,我相信大家就能清楚的明白了。
//宣告一個Person介面
public interface Person {
void smile(int time);
}
//在java8以前我們實現介面
Person p = new Person() {
@Override
public void smile(int time) {
System.out.println("我笑了" + time + "秒");
}
};
//通過Lambda實現介面
Person p = time -> System.out.println("我笑了" + time + "秒");
複製程式碼
觀察上述程式碼,我們發現。通過Lambda表示式來實現介面,相比之前的實現介面的方式,Lambda表示式是程式碼更加清晰,且程式碼量較少。
1.2.1 Lambda表示式表達的方法個數。
說到這裡,我相信大家都有一個疑問,就是假如Person介面中不止有一個smile方法,還有其他方法呢?那這個時候我該怎麼用Lambda表示式來表示呢? 這個問題其實在最初的Lambda表示式分析那裡已經側面表現了。既然是將函式賦值給變數,那麼一個變數對應的函式就只能有且只有一個,不然怎麼知道你在呼叫的時候具體呼叫的哪個函式呢?你說呢,兄die.
說到這裡,有的兄die就會說,我怎麼才能讓我的介面有且只有一個方法呢?,那就引出我們的**@FunctionalInterface**註解,直接通過英文直譯的話意思是“函式式介面”。我去,什麼鬼,不瞭解其意思,我們就直接看原始碼吧。
An informative annotation type used to indicate that an interface
* type declaration is intended to be a <i>functional interface</i> as
* defined by the Java Language Specification.
*
* Conceptually, a functional interface has exactly one abstract
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
複製程式碼
這裡我擷取了部分FunctionalInterface的部分註釋,@FunctionalInterface 註解主要是用於修飾介面。且修飾的介面中有且只有一個方法,到這裡我們就明白了,那麼我可以用**@FunctionalInterface**註解來修飾我們的介面啦。這個介面是不是很牛逼啊。
二、Lambda表示式語法
在瞭解Lambda的由來,以及表現形式後,我們現在可以來了解一下Lambda的語法啦,知道原理,當然也必須知道怎麼使用,對吧。(授人魚不如授人以漁,你說是不是這個道理,哈哈哈,秀秀我的語文功底)。
- Lambad表示式總是在花括號中;
- 其引數(如果有的話)在 —>之前宣告(引數型別可以省略);
- 函式體(如果存在的話)在—>之後宣告;
基本語法:
(formal parameter list) -> { expression or statements}
複製程式碼
2.1 Lambda表示式例子
Lambda 表示式實際上是一種匿名方法實現(有的人說是匿名內部類實現,看個人怎麼理解了),Lambda表示式的求值不會導致函式體的執行,而是在呼叫該方法後發生,下面是一些Lambda表示式的例子:
() -> {} //無參,空的函式體⑤
() -> 42 //無參,函式體返回42⑥
() -> null // 無參,函式體返回null⑦
() -> { return 42; } // 無參,函式體返回42
() -> { System.gc(); } // 無參,執行語句,函式體返Void
() -> System.gc() // 無參,執行語句,函式體返Void
() -> { // 帶返回語句的完整函式體
if (true) return 12;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // 宣告型別的單個引數⑧
(int x) -> { return x+1; } // 宣告型別的單個引數
(x) -> x+1 // 隱藏引數型別的單個引數③
x -> x+1 // 花括號可選④
(String s) -> s.length() // 宣告型別的單個引數
(Thread t) -> { t.start(); } // 宣告型別的單個引數
s -> s.length() // 隱藏引數型別的單個引數
t -> { t.start(); } // 隱藏引數型別的單個引數⑩
(int x, int y) -> x+y // 宣告引數型別的多個引數①
(x, y) -> x+y // 隱藏引數型別的多個引數②
(x, int y) -> x+y // 錯誤:不能同時單個的宣告型別或隱藏型別
(x, final y) -> x+y // 錯誤:沒有推斷型別的修飾符
複製程式碼
相信大家從上面例子中可以看出一下語法規則,那我們將這語法規則分為兩個部分Lambda引數規則與lambda函式體規則。
2.2 Lambda引數規則
- 引數列表必須是以逗號分隔的形式。見①
- 引數列表的引數型別是可選的,如果未指定引數型別,將從上下文進行推斷。見②
- 引數列表引數的個數大於2則必須用小括號括起來 。見②
- 引數列表引數的個數為1,則小括號可以省略。見⑩
- 如果函式體中,沒有使用引數,那麼必須指定空括號。見⑤⑥⑦
2.3 Lambda函式體規則
- 如果函式體只包含單條表示式或語句,就不需要使用大括號。見⑧⑨
三、 Lambda使用注意事項
現在我們基本瞭解了Lambda的使用方式,已經使用場景,那麼現在我們看看在Lambda使用中需要注意的問題。
3.1 Lambda訪問變數為final型別
我們都知道在匿名內部類中訪問變數時,需要將變數修飾為final型別。因為如果該該內部類執行在另一執行緒中,必將會出現執行緒安全的問題。(這裡肯定有很多讀者就會提出這樣一個問題,那這個內部類修飾變數為final和我們Lambda表示式有毛關係啊?)請聽我細細道來。
如果你仔細閱讀了該文,你應該知道Lambda表示式相當於宣告匿名內部類。只是換了一種表達方式而已。那麼本質是一樣的。所以在訪問變數的時候我們也需要注意這些問題。看下面程式碼。
public interface A {
void fuck();
}
public void test() {
int count = 10;
A a = () -> { count++};//錯誤 count 型別為final型別。
}
複製程式碼
觀察上述程式碼,大家肯定會疑惑,為什麼我這裡沒有申明count為final型別。因為在java8中,在編譯器編譯的時候會將Lambda表示式訪問的變數,自動宣告為final型別。有點語法糖的味道。
總結
通過上文分析。我們發現Lambda表示式不僅使程式變得更加簡潔。還能節省程式設計師的程式碼量,節省了很多碼程式碼的時間。再有一個我覺得更重要的是,這樣程式碼看起來很騷,你說是嗎?
參考
站在巨人的肩膀上。可以看得更遠。感謝下列博主,和官方文件。人類的知識是大家的。
最後,附上我寫的一個基於Kotlin 仿開眼的專案SimpleEyes(ps: 其實在我之前,已經有很多小朋友開始仿這款應用了,但是我覺得要做就做好。所以我的專案和其他的人應該不同,不僅僅是簡單的一個應用。但是,但是。但是。重要的話說三遍。還在開發階段,不要打我),歡迎大家follow和start