# 六角鼠年鐵人賽 Week 16 - Spring Boot - 番外篇 Java 8 Lambda Tutorial
==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai==
## 劇中佳句 一代宗師
:::info
念念不忘,必有回響
:::
## 主題
這週與下週都會各自寫個番外篇特別來介紹 Java 8 的 Lambda expression 和 Stream 的學習心得分享。
為什麼要特別提到這兩個 feature 呢? 因為對 Java 來說,從 7 -> 8 可以說是 Java 史上最大幅度的改變! 而當中最大的改變莫屬這兩個 feature 不可。
到底他們有多大的能耐可以造就這歷史級別的差異,就在文中慢慢來介紹了~
:::info
**小知識分享**
- Java 8 -> 9 也是有不少的內容改變和新 feature 加入,但考慮到 **歷史事件**( Oracle 收購昇陽、Java 開始收費)、**市場考量**(OpenJDK 的出現) 等因素,7 -> 8 的幅度切切實實的影響了所有的 Java 程式設計師。
:::
時至今日,仍有不少金融企業的內部系統採用穩定的 Java 7;新一點的科技公司會升級使用到 Java 8;部分新創公司則開始嘗試導入 Java 11 的版本。
而為什麼大部分的 Java 系統還在採用 7 或 8 這種老舊的版本呢? 這是因為在眾多的 Java Plugin 使用上,大部分的套件都還是為 7 或 8 版本量身打造(因為這兩版本使用率最高),這些套件也沒有因為 Java 版本升級而去更新,部分會在 Java 版本跳升到使用 9 (甚至是 up 版本)時會有許多錯誤跑出,諸如 **Mavan**, **Gradle** 這類時常更新的套件也受到版本影響。
在企業管理的考量下,自然不會考慮到做升級的打算,畢竟面對無法預期的錯誤與可能造成的損失來看,這個投資性價比十分的低,因此大多公司若非必要是不會進行系統升級的。 (很多都是另起爐灶最快)
因此對於剛投入職場的 Java 工程師們,如果你是學習非常新穎的版本 (例如9, 10, 11, 12, 13, 14...),可能需要重新學習並去習慣較不方便的 7 或 8 的版本了。
> Java 7 在 2015年 4月 發出停止更新版本公告
> Java 8 在 2019年 1月 發出停止更新版本公告
## Lambda 表示式
英文全名為 **Lambda Expression**,講到 Lambda 表示式,就必須連同 **Functional Interfaces** 和 **匿名類別**一起講,各位看官才會有感覺,因為透過 Lambda 表示式,將會大大改善使用 Functional Interfaces 和 匿名類別的方式。
:::info
### Functional Interfaces
並不是 Java 8 的新 feature,而是由 Interface class 衍生的一種新型態的 interface,強調快速使用為導向,是為了 Lambda 表示式而被強化的 interface,使用 Annotation @FunctionalInterface 進行 Class 的標註,其設計上的限制為該 Interface Class **僅能設計持有一支 abstract method**
例如:
```java=
@FunctionalInterface
public class testObject{
public void doTest();
// 若增加以下程式碼則會跳出錯誤: Unexpeted @FunctionalInterface Annotation
//public void doTest2();
}
```
> Java 8 版本可以在 interface class 中設計實作方法,設計上的限制為必須使用 **default** 或是 **static** 的 method 宣告
> 例如:
> ```java=
> @FunctionalInterface
> public class testObject{
> public void doTest();
>
> // 一般類別成員方法
> public default void doCheck(){
> System.out.println("Checked!");
> }
>
> // 靜態類別方法
> public static void doCheck2(){
> System.out.println("Checked2!");
> }
> }
> ```
若一個 interface 繼承一個 functional interface 時的狀況:
- 在**不增加** abstract method 的情況下會在 Coding 時,**被視為 functional interface**
- 在**增加 default** 或是 **static** 等方法情況下,**仍然被視為 functional interface**
- 在**增加** abstract method 的情況下,會**不再被視為 functional interface**,後續無法使用 Lambda 表示式使用該 interface,必須遵照原本 interface 的使用方式。
:::
:::info
### 匿名類別
用來滿足系統實作時,面臨一次性修改且不更動原 Class 內容、透過繼承原類別或實作某些 Interface 的類別後進行修改並建成 Inner Class 存於該使用的 Class 中,並且這些 Inner Class 不需要被引用,我們就會將其設計成匿名內部類別,以方便我們繼續開發系統,避免過多一次性使用的類別而造成整體檔案架構的凌亂。
舉例大家常見的 String 為例子:
```java=
Object s1 = new Object(){
@Override
public String toString(){
return "I Override and update the content just in this String class.";
}
};
System.out.println("This is S1: " + s1.toString());
Object s2 = "check toString method.";
System.out.println("This is S2: " + s2.toString());
```
```
Execute Result:
This is S1: I Override and update the content just in this String class.
This is S2: check toString method.
```
從上述例子中大家可以清楚發現,Kai 因為需求關係,必須要修正 s1 物件的 toString() 這個方法,因此我直接在建立物件的同時去 Override 這個方法的內容,這就是一種典型的匿名內部類別,JVM 會在 Compile 該 Class 的同時在裡面建立一個新的 Inner Class 並繼承 Object 物件,然後將設計師寫的 Override 方法給放入其中。
這樣的修改僅會是一次性、專屬性的存在該 Class 當中,可以從 s1 與 s2 兩個物件最後都是呼叫 toString() 的差異看出端倪。
:::
## 標準寫法
```java=
// 無參數 無回傳 多行
() -> {
System.out.println("first line.");
System.out.println("second line.");
}
// 無參數 無回傳 單行
() -> System.out.println("first line.");
//有參數 有回傳 多行
(Integer i1,Integer i2) -> {
i1 = i1 * 2;
i2 = i2 * 3;
return i1 + i2;
}
// 有參數 有回傳 單行
(Integer i1,Integer i2) -> i1 + i2;
// 單參數 有回傳 多行
Integer i1 -> {
i1 = Math.abs(i1);
return i1 * 3;
}
// 單參數 有回傳 單行
Integer i1 -> Math.abs(i1) * 3;
```
## 有無回傳值?
特別拿出來強調的一個標題,Lambda 表示式是用來方便開發匿名內部類別用的設計方式,不是用來寫完整新的 function 的東西!
**所以用作參考原型的方法有無回傳,在 Lambda 表示式中會保持一樣的格式!!**
**所以用作參考原型的方法有無回傳,在 Lambda 表示式中會保持一樣的格式!!**
**所以用作參考原型的方法有無回傳,在 Lambda 表示式中會保持一樣的格式!!**
很重要,強調三遍!
**Lambda 表示式可以改變的是內部程序,無法改變傳入、傳出的參數、方法的宣告和名稱**
**Lambda 表示式可以改變的是內部程序,無法改變傳入、傳出的參數、方法的宣告和名稱**
**Lambda 表示式可以改變的是內部程序,無法改變傳入、傳出的參數、方法的宣告和名稱**
再強調三遍!
## 優缺點?!
有好就有壞,而一個東西不論好壞,只要能夠使用在適當的地方,那就成功了一半!
在學習 Lambda 表示式的同時,我們必須了解這種設計方式所帶來的好與壞,才能將其妥善的運用在系統開發中
- 優點:
- 大幅減少程式行數
- 增加程式閱讀性
- 增加運行效率
> 一般匿名內部類別,JVM compile 後會在檔案中產生一組 class 檔,但 Lambda 表示式則不會產出新檔案
- 缺點:
- 需要了解其使用的 interface
- 偵錯時的中斷點不好釘標
## 常見的 Functional Interfaces
為了順應 Lambda 表示式的到來,許多我們以往常見的 interface 也已經在 Java 8 被調整為 Functional Interface 型態了。
### Runnable
參數: Null
回傳: void
```java=
Runnable r = () -> {System.out.println("Runnable with Lambda Expression");};
```
### Predicate
參數: T
回傳: Boolean
```java=
Predicate<String> p = string -> string.length > 3;
```
### Consumer
參數: T
回傳: void
```java=
Consumner<String> c = string -> System.out.println(string);
```
### Function
參數: T
回傳: R
```java=
Function<String,Integer> f = string -> string.length;
```
### Supplier
參數: Null
回傳: T
```java=
Supplier<List<String>> s = () -> new ArrayList<String>();
```
### UnaryOperator
參數: T
回傳: T
```java=
UnaryOperator<Integer> uo = (num) -> num + 1 ;
```
### BinaryOperator
參數: (T,T)
回傳: T
```java=
BinaryOperator<Interger> bo = (num1 , num2) -> num1 + num2;
```
## Functional Interface 實例分享
```java=
package kai.com.lambda;
public class MathMainTest {
public static void main(String []args) {
MathProcess mp = i1 -> Math.abs(i1) * 10;
System.out.println("mp Result: " + mp.doOperator(10));
MathProcess2 mp2_add = (i1, i2) -> i1 + i2;
MathProcess2 mp2_subtract = (i1, i2) -> i1 - i2;
MathProcess2 mp2_multiply = (i1, i2) -> i1 * i2;
MathProcess2 mp2_divided = (i1, i2) -> i1 / i2;
MathProcess3 mp3 = (i1, i2, mp2) -> mp2.doOperator(i1, i2);
System.out.println("Add Way: " + mp3.getResult(10, 5, mp2_add));
System.out.println("subtract Way: " + mp3.getResult(10, 5, mp2_subtract));
System.out.println("multiply Way: " + mp3.getResult(10, 5, mp2_multiply));
System.out.println("divided Way: " + mp3.getResult(10, 5, mp2_divided));
}
}
interface MathProcess {
public Integer doOperator(int i);
}
interface MathProcess2 {
public Integer doOperator(int i, int i2);
}
interface MathProcess3 {
public Integer getResult(int i1 , int i2, MathProcess2 mp2);
}
```
Kai 這邊寫了三個 interface 都放在 MathMainTest 底下,並在第一段,單純展示 Lambda 的應用,以往需要複雜再定義的匿名內部類別寫法,只需要透過 Lambda 短短一行即可實現目的。
```java=
MathProcess mp = i1 -> Math.abs(i1) * 10;
System.out.println("mp Result: " + mp.doOperator(10));
```
再來是第二部分,Kai 透過兩個 interface 去組件出多種內部類別方法的可能性。
使用 mp2 interface 專門做出不同變化的程序方法,再將其透過 mp3 interface 轉變為統一呼叫使用的模組。
這種作業方式將會大大增強程式之間的彈性,mp2 可因應不同狀況實作不同程序給不同的 Class,而後,mp3 僅需加入包含有 mp2 的 class 與其要求的參數,即可充分運用個別 mp2 method 物件。
```java=
MathProcess2 mp2_add = (i1, i2) -> i1 + i2;
MathProcess2 mp2_subtract = (i1, i2) -> i1 - i2;
MathProcess2 mp2_multiply = (i1, i2) -> i1 * i2;
MathProcess2 mp2_divided = (i1, i2) -> i1 / i2;
MathProcess3 mp3 = (i1, i2, mp2) -> mp2.doOperator(i1, i2);
System.out.println("Add Way: " + mp3.getResult(10, 5, mp2_add));
System.out.println("subtract Way: " + mp3.getResult(10, 5, mp2_subtract));
System.out.println("multiply Way: " + mp3.getResult(10, 5, mp2_multiply));
System.out.println("divided Way: " + mp3.getResult(10, 5, mp2_divided));
```
## Collection 實例分享
```java=
package kai.com.lambda;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CollectionTest {
public static void main(String [] args){
Map<String,String> map = new HashMap<>();
map.put("1","first");
map.put("2","second");
map.put("3","third");
map.forEach((k, v) -> System.out.println("Map get: " + k + ":" + v));
List<String> list = new ArrayList<>();
list.add("first");
list.add("second");
list.add("third");
list.forEach((k) -> System.out.println("List get: " + k));
}
}
```
Collection 是非常非常使用的類別,舉凡 List, Set, Map 都包含在內,而在 Java 8 版本中,更特別增加了
- MAP:
```java=
public void forEach(java.util.function.BiConsumer<? super K, ? super V> action)
```
- LIST:
```java=
public void forEach(java.util.function.Consumer<? super T> action)
```
等方法可以讓開發者進行 Lambda 表示式的覆寫,讓其使用在 for loop 中的應用更加方便。
## 結語
:::danger
其餘還有很多 Lambda 的應用,Kai 這邊單純做介紹 Lambda 和其部分應用即可,後續還有一篇會特別來介紹一下 Stream,這個也是跟 Lambda 的應用非常相關的 feature。
[六角鼠年鐵人賽 Week 17 - Spring Boot - 番外篇 Java 8 Stream Tutorial](/l4MvMeuATGWcEzhGTDrGRQ)
:::
首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA)
###### tags: `Spring Boot`,`w3HexSchool`