1. 程式人生 > >Lambda表示式你會用嗎?

Lambda表示式你會用嗎?

## 函數語言程式設計 在正式學習Lambda之前,我們先來了解一下什麼是函數語言程式設計 我們先看看什麼是函式。函式是一種最基本的任務,一個大型程式就是一個頂層函式呼叫若干底層函式,這些被呼叫的函式又可以呼叫其他函式,即大任務被一層層拆解並執行。所以函式就是面向過程的程式設計的基本單元。 Java不支援單獨定義函式,但可以把靜態方法視為獨立的函式,把例項方法視為自帶`this`引數的函式。而函數語言程式設計(請注意多了一個“式”字)——Functional Programming,雖然也可以歸結到面向過程的程式設計,但其思想更接近數學計算。 我們首先要搞明白計算機(Computer)和計算(Compute)的概念。在計算機的層次上,CPU執行的是加減乘除的指令程式碼,以及各種條件判斷和跳轉指令,所以,組合語言是最貼近計算機的語言。而計算則指數學意義上的計算,越是抽象的計算,離計算機硬體越遠。對應到程式語言,就是越低階的語言,越貼近計算機,抽象程度低,執行效率高,比如C語言;越高階的語言,越貼近計算,抽象程度高,執行效率低,比如Lisp語言。 函數語言程式設計就是一種抽象程度很高的程式設計正規化,純粹的函數語言程式設計語言編寫的函式沒有變數,因此,任意一個函式,只要輸入是確定的,輸出就是確定的,這種純函式我們稱之為沒有副作用。而允許使用變數的程式設計語言,由於函式內部的變數狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函式是有副作用的。 函數語言程式設計的一個特點就是,允許把函式本身作為引數傳入另一個函式,還允許返回一個函式! 函數語言程式設計最早是數學家[阿隆佐·邱奇](https://zh.wikipedia.org/wiki/阿隆佐·邱奇)研究的一套函式變換邏輯,又稱Lambda Calculus(λ-Calculus),所以也經常把函數語言程式設計稱為Lambda計算。 Java平臺從Java 8開始,支援函數語言程式設計。 ## Lambda初體驗 先從一個例子開始,讓我們來看一下Lambda可以用在什麼地方。 ### 例一:建立執行緒 常見建立執行緒的方法(JDK1.8以前) ```java //JDK1.7通過匿名內部類的方式建立執行緒 Thread thread = new Thread(new Runnable() { @Override public void run() { //實現run方法 System.out.println("Thread Run..."); } }); thread.start(); ``` 通過匿名內部類的方式建立執行緒,省去了取名字的煩惱,但是還能不能再簡化一些呢? JDK1.8 Lambda表示式寫法 ```java Thread thread = new Thread(() -> System.out.println("Thread Run")); //一行搞定 thread.start(); ``` 我們可以看到Lambda一行程式碼就完成了執行緒的建立,簡直不要太方便。(至於Lambda表示式的語法,我們下面章節再詳細介紹) 如果你的邏輯不止一行程式碼,那麼你還可以這麼寫 ```java Thread thread = new Thread(() -> { System.out.println("Thread Run"); System.out.println("Hello"); }); thread.start(); ``` 用`{}`將程式碼塊包裹起來 ### 例二:自定義比較器 我們先來看一下JDK1.7是如何實現自定義比較器的 ```java List list = Arrays.asList("Hi", "Life", "Hello~", "World"); Collections.sort(list, new Comparator(){// 介面名 @Override public int compare(String s1, String s2){// 方法名 if(s1 == null) return -1; if(s2 == null) return 1; return s1.length()-s2.length(); } }); //輸出排序好的List for (String s : list) { System.out.println(s); } ``` 這裡的sort方法傳入了兩個引數,一個是待排序的list,一個是比較器(排序規則),這裡也是通過匿名內部類的方式實現的比較器。 下面我們來看一下Lambda表示式如何實現比較器? ```java List list = Arrays.asList("Hi", "Life", "Hello~", "World"); Collections.sort(list, (s1, s2) ->{// 省略了引數的型別,編譯器會根據上下文資訊自動推斷出型別 if(s1 == null) return -1; if(s2 == null) return 1; return s1.length()-s2.length(); }); //輸出排序好的List for (String s : list) { System.out.println(s); } ``` 我們可以看到,Lambda表示式和匿名內部類的作用相同,但是省略了很多程式碼,可以大大加快開發速度 ## Lambda表示式語法 Lambda 表示式,也可稱為閉包,它是推動 Java 8 釋出的最重要新特性。Lambda 允許把函式作為一個方法的引數(函式作為引數傳遞進方法中)。 使用 Lambda 表示式可以使程式碼變的更加簡潔緊湊。上一章節我們已經見識到了Lambda表示式的優點,那麼Lambda表示式到底該怎麼寫呢? ### 語法 lambda 表示式的語法格式如下: ```java (parameters) -> expression //一行程式碼 或 (parameters) ->{ statements; } //多行程式碼 ``` lambda表示式的重要特徵: - **可選型別宣告:**不需要宣告引數型別,編譯器可以統一識別引數值。 - **可選的引數圓括號:**一個引數無需定義圓括號,但多個引數需要定義圓括號。 - **可選的大括號:**如果主體包含了一個語句,就不需要使用大括號。 - **可選的返回關鍵字:**如果主體只有一個表示式返回值則編譯器會自動返回值,大括號需要指定明表示式返回了一個數值。 ```java // 1. 不需要引數,返回值為 5 () -> 5 // 2. 接收一個引數(數字型別),返回其2倍的值 x -> 2 * x // 3. 接受2個引數(數字),並返回他們的差值 (x, y) -> x – y // 4. 接收2個int型整數,返回他們的和 (int x, int y) -> x + y // 5. 接受一個 string 物件,並在控制檯列印,不返回任何值(看起來像是返回void) (String s) -> System.out.print(s) ``` ## 函式介面 上面幾個章節給大家介紹Lambda表示式的基本使用,那麼是不是在任意地方都可以使用Lambda表示式呢? 其實Lambda表示式使用是有限制的。也許你已經想到了,**能夠使用Lambda的依據是必須有相應的函式介面**。(函式介面,是指內部只有一個抽象方法的介面) ### 自定義函式介面 自定義函式介面很容易,只需要編寫一個只有一個抽象方法的介面即可。 ```java // 自定義函式介面 @FunctionalInterface public interface PersonInterface{ void accept(T t); } ``` 上面程式碼中的@FunctionalInterface是可選的,但加上該標註編譯器會幫你檢查介面是否符合函式介面規範。就像加入@Override標註會檢查是否過載了函式一樣。 那麼根據上面的自定義函式式介面,我們就可以寫出如下的Lambda表示式。 ```java PersonInterface p = str -> System.out.println(str); ``` ## Lambda和匿名內部類 經過上面幾部分的介紹,相信大家對Lambda表示式已經有了初步認識,學會了如何使用。但想必大家心中始終有一個疑問,Lambda表示式似乎只是為了簡化匿名內部類的寫法,其他也沒啥區別了。這看起來僅僅通過語法糖在編譯階段把所有的Lambda表示式替換成匿名內部類就可以了,事實真的如此嗎? ```java public class Main { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("Anonymous class"); } }).start(); } } ``` 匿名內部類也是一個類,只不過我們不需要顯示為他定義名稱,但是編譯器會自動為匿名內部類命名。Main編輯後的檔案如下圖 ![image-20201217164035157](https://cdn.jsdelivr.net/gh/wugongzi-git/BlogFigurebed@master/image/image-20201217164035157.png) 我們可以看到共有兩個class檔案,一個是Main.class,而另一個則是編輯器為我們命名的內部類。 下面我們來看一下Lambda表示式會產生幾個class檔案 ```java public class Main { public static void main(String[] args) { new Thread(() -> System.out.println("Lambda")).start(); } } ``` ![image-20201217164610350](https://cdn.jsdelivr.net/gh/wugongzi-git/BlogFigurebed@master/image/image-20201217164610350.png) Lambda表示式通過invokedynamic指令實現,書寫Lambda表示式不會產生新的類 ## Lambda在集合中的運用 > 既然Lambda表示式這麼方便,那麼哪些地方可以使用Lambda表示式呢? 我們先從最熟悉的*Java集合框架(Java Collections Framework, JCF)*開始說起。 為引入Lambda表示式,Java8新增了`java.util.funcion`包,裡面包含常用的**函式介面**,這是Lambda表示式的基礎,Java集合框架也新增部分介面,以便與Lambda表示式對接。 首先回顧一下Java集合框架的介面繼承結構: ![JCF_Collection_Interfaces](https://gitee.com/objcoding/md-picture/raw/master/img/JCF_Collection_Interfaces.png) 上圖中綠色標註的介面類,表示在Java8中加入了新的介面方法,當然由於繼承關係,他們相應的子類也都會繼承這些新方法。下表詳細列舉了這些方法。 | 介面名 | Java8新加入的方法 | | :--------- | :----------------------------------------------------------- | | Collection | removeIf() spliterator() stream() parallelStream() forEach() | | List | replaceAll() sort() | | Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() | 這些新加入的方法大部分要用到`java.util.function`包下的介面,這意味著這些方法大部分都跟Lambda表示式相關。我們將逐一學習這些方法。 ### Collection中的新方法 如上所示,介面`Collection`和`List`新加入了一些方法,我們以是`List`的子類`ArrayList`為例來說明。瞭解[Java7`ArrayList`實現原理](https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/2-ArrayList.md),將有助於理解下文。 #### forEach() 該方法的簽名為`void forEach(Consumer action)`,作用是對容器中的每個元素執行`action`指定的動作,其中`Consumer`是個函式介面,裡面只有一個待實現方法`void accept(T t)`(後面我們會看到,這個方法叫什麼根本不重要,你甚至不需要記憶它的名字)。 需求:*假設有一個字串列表,需要打印出其中所有長度大於3的字串.* Java7及以前我們可以用增強的for迴圈實現: ```java // 使用曾強for迴圈迭代 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); for(String str : list){ if(str.length()>3) System.out.println(str); } ``` 現在使用`forEach()`方法結合匿名內部類,可以這樣實現: ```java // 使用forEach()結合匿名內部類迭代 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.forEach(new Consumer(){ @Override public void accept(String str){ if(str.length()>3) System.out.println(str); } }); ``` 上述程式碼呼叫`forEach()`方法,並使用匿名內部類實現`Comsumer`介面。到目前為止我們沒看到這種設計有什麼好處,但是不要忘記Lambda表示式,使用Lambda表示式實現如下: ```java // 使用forEach()結合Lambda表示式迭代 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.forEach( str -> { if(str.length()>3) System.out.println(str); }); ``` 上述程式碼給`forEach()`方法傳入一個Lambda表示式,我們不需要知道`accept()`方法,也不需要知道`Consumer`介面,型別推導幫我們做了一切。 #### removeIf() 該方法簽名為`boolean removeIf(Predicate filter)`,作用是**刪除容器中所有滿足`filter`指定條件的元素**,其中`Predicate`是一個函式介面,裡面只有一個待實現方法`boolean test(T t)`,同樣的這個方法的名字根本不重要,因為用的時候不需要書寫這個名字。 需求:*假設有一個字串列表,需要刪除其中所有長度大於3的字串。* 我們知道如果需要在迭代過程衝對容器進行刪除操作必須使用迭代器,否則會丟擲`ConcurrentModificationException`,所以上述任務傳統的寫法是: ```java // 使用迭代器刪除列表元素 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Iterator it = list.iterator(); while(it.hasNext()){ if(it.next().length()>3) // 刪除長度大於3的元素 it.remove(); } ``` 現在使用`removeIf()`方法結合匿名內部類,我們可是這樣實現: ```java // 使用removeIf()結合匿名名內部類實現 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.removeIf(new Predicate(){ // 刪除長度大於3的元素 @Override public boolean test(String str){ return str.length()>3; } }); ``` 上述程式碼使用`removeIf()`方法,並使用匿名內部類實現`Precicate`介面。相信你已經想到用Lambda表示式該怎麼寫了: ```java // 使用removeIf()結合Lambda表示式實現 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.removeIf(str -> str.length()>3); // 刪除長度大於3的元素 ``` 使用Lambda表示式不需要記憶`Predicate`介面名,也不需要記憶`test()`方法名,只需要知道此處需要一個返回布林型別的Lambda表示式就行了。 #### replaceAll() 該方法簽名為`void replaceAll(UnaryOperator operator)`,作用是**對每個元素執行`operator`指定的操作,並用操作結果來替換原來的元素**。其中`UnaryOperator`是一個函式介面,裡面只有一個待實現函式`T apply(T t)`。 需求:*假設有一個字串列表,將其中所有長度大於3的元素轉換成大寫,其餘元素不變。* Java7及之前似乎沒有優雅的辦法: ```java // 使用下標實現元素替換 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); for(int i=0; i3) list.set(i, str.toUpperCase()); } ``` 使用`replaceAll()`方法結合匿名內部類可以實現如下: ```java // 使用匿名內部類實現 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.replaceAll(new UnaryOperator(){ @Override public String apply(String str){ if(str.length()>3) return str.toUpperCase(); return str; } }); ``` 上述程式碼呼叫`replaceAll()`方法,並使用匿名內部類實現`UnaryOperator`介面。我們知道可以用更為簡潔的Lambda表示式實現: ```java // 使用Lambda表示式實現 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.replaceAll(str -> { if(str.length()>3) return str.toUpperCase(); return str; }); ``` #### sort() 該方法定義在`List`介面中,方法簽名為`void sort(Comparator c)`,該方法**根據`c`指定的比較規則對容器元素進行排序**。`Comparator`介面我們並不陌生,其中有一個方法`int compare(T o1, T o2)`需要實現,顯然該介面是個函式介面。 需求:*假設有一個字串列表,按照字串長度增序對元素排序。* 由於Java7以及之前`sort()`方法在`Collections`工具類中,所以程式碼要這樣寫: ```java // Collections.sort()方法 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); Collections.sort(list, new Comparator(){ @Override public int compare(String str1, String str2){ return str1.length()-str2.length(); } }); ``` 現在可以直接使用`List.sort()方法`,結合Lambda表示式,可以這樣寫: ```java // List.sort()方法結合Lambda表示式 ArrayList list = new ArrayList<>(Arrays.asList("I", "love", "you", "too")); list.sort((str1, str2) -> str1.length()-str2.length()); ``` #### spliterator() 方法簽名為`Spliterator spliterator()`,該方法返回容器的**可拆分迭代器**。從名字來看該方法跟`iterator()`方法有點像,我們知道`Iterator`是用來迭代容器的,`Spliterator`也有類似作用,但二者有如下不同: 1. `Spliterator`既可以像`Iterator`那樣逐個迭代,也可以批量迭代。批量迭代可以降低迭代的開銷。 2. `Spliterator`是可拆分的,一個`Spliterator`可以通過呼叫`Spliterator trySplit()`方法來嘗試分成兩個。一個是`this`,另一個是新返回的那個,這兩個迭代器代表的元素沒有重疊。 可通過(多次)呼叫`Spliterator.trySplit()`方法來分解負載,以便多執行緒處理。 #### stream()和parallelStream() `stream()`和`parallelStream()`分別**返回該容器的`Stream`視圖表示**,不同之處在於`parallelStream()`返回並行的`Stream`。**`Stream`是Java函數語言程式設計的核心類**,我們會在後面章節中學習。 ### Map中的新方法 相比`Collection`,`Map`中加入了更多的方法,我們以`HashMap`為例來逐一探祕。瞭解[Java7`HashMap`實現原理](https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/6-HashSet and HashMap.md),將有助於理解下文。 #### forEach() 該方法簽名為`void forEach(BiConsumer action)`,作用是**對`Map`中的每個對映執行`action`指定的操作**,其中`BiConsumer`是一個函式介面,裡面有一個待實現方法`void accept(T t, U u)`。`BinConsumer`介面名字和`accept()`方法名字都不重要,請不要記憶他們。 需求:*假設有一個數字到對應英文單詞的Map,請輸出Map中的所有對映關係.* Java7以及之前經典的程式碼如下: ```java // Java7以及之前迭代Map HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for(Map.Entry entry : map.entrySet()){ System.out.println(entry.getKey() + "=" + entry.getValue()); } ``` 使用`Map.forEach()`方法,結合匿名內部類,程式碼如下: ```java // 使用forEach()結合匿名內部類迭代Map HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.forEach(new BiConsumer(){ @Override public void accept(Integer k, String v){ System.out.println(k + "=" + v); } }); ``` 上述程式碼呼叫`forEach()`方法,並使用匿名內部類實現`BiConsumer`介面。當然,實際場景中沒人使用匿名內部類寫法,因為有Lambda表示式: ```java // 使用forEach()結合Lambda表示式迭代Map HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.forEach((k, v) -> System.out.println(k + "=" + v)); } ``` #### getOrDefault() 該方法跟Lambda表示式沒關係,但是很有用。方法簽名為`V getOrDefault(Object key, V defaultValue)`,作用是**按照給定的`key`查詢`Map`中對應的`value`,如果沒有找到則返回`defaultValue`**。使用該方法程式設計師可以省去查詢指定鍵值是否存在的麻煩. 需求;*假設有一個數字到對應英文單詞的Map,輸出4對應的英文單詞,如果不存在則輸出NoValue* ```java // 查詢Map中指定的值,不存在時使用預設值 HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); // Java7以及之前做法 if(map.containsKey(4)){ // 1 System.out.println(map.get(4)); }else{ System.out.println("NoValue"); } // Java8使用Map.getOrDefault() System.out.println(map.getOrDefault(4, "NoValue")); // 2 ``` #### putIfAbsent() 該方法跟Lambda表示式沒關係,但是很有用。方法簽名為`V putIfAbsent(K key, V value)`,作用是隻有在**不存在`key`值的對映或對映值為`null`時**,才將`value`指定的值放入到`Map`中,否則不對`Map`做更改.該方法將條件判斷和賦值合二為一,使用起來更加方便. #### remove() 我們都知道`Map`中有一個`remove(Object key)`方法,來根據指定`key`值刪除`Map`中的對映關係;Java8新增了`remove(Object key, Object value)`方法,只有在當前`Map`中**`key`正好對映到`value`時**才刪除該對映,否則什麼也不做. #### replace() 在Java7及以前,要想替換`Map`中的對映關係可通過`put(K key, V value)`方法實現,該方法總是會用新值替換原來的值.為了更精確的控制替換行為,Java8在`Map`中加入了兩個`replace()`方法,分別如下: - `replace(K key, V value)`,只有在當前`Map`中**`key`的對映存在時**才用`value`去替換原來的值,否則什麼也不做. - `replace(K key, V oldValue, V newValue)`,只有在當前`Map`中**`key`的對映存在且等於`oldValue`時**才用`newValue`去替換原來的值,否則什麼也不做. #### replaceAll() 該方法簽名為`replaceAll(BiFunction function)`,作用是對`Map`中的每個對映執行`function`指定的操作,並用`function`的執行結果替換原來的`value`,其中`BiFunction`是一個函式介面,裡面有一個待實現方法`R apply(T t, U u)`.不要被如此多的函式介面嚇到,因為使用的時候根本不需要知道他們的名字. 需求:*假設有一個數字到對應英文單詞的Map,請將原來對映關係中的單詞都轉換成大寫.* Java7以及之前經典的程式碼如下: ```java // Java7以及之前替換所有Map中所有對映關係 HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); for(Map.Entry entry : map.entrySet()){ entry.setValue(entry.getValue().toUpperCase()); } ``` 使用`replaceAll()`方法結合匿名內部類,實現如下: ```java // 使用replaceAll()結合匿名內部類實現 HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.replaceAll(new BiFunction(){ @Override public String apply(Integer k, String v){ return v.toUpperCase(); } }); ``` 上述程式碼呼叫`replaceAll()`方法,並使用匿名內部類實現`BiFunction`介面。更進一步的,使用Lambda表示式實現如下: ```java // 使用replaceAll()結合Lambda表示式實現 HashMap map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); map.replaceAll((k, v) -> v.toUpperCase()); ``` 簡潔到讓人難以置信. #### merge() 該方法簽名為`merge(K key, V value, BiFunction remappingFunction)`,作用是: 1. 如果`Map`中`key`對應的對映不存在或者為`null`,則將`value`(不能是`null`)關聯到`key`上; 2. 否則執行`remappingFunction`,如果執行結果非`null`則用該結果跟`key`關聯,否則在`Map`中刪除`key`的對映. 引數中`BiFunction`函式介面前面已經介紹過,裡面有一個待實現方法`R apply(T t, U u)`. `merge()`方法雖然語義有些複雜,但該方法的用方式很明確,一個比較常見的場景是將新的錯誤資訊拼接到原來的資訊上,比如: ```java map.merge(key, newMsg, (v1, v2) -> v1+v2); ``` #### compute() 該方法簽名為`compute(K key, BiFunction remappingFunction)`,作用是把`remappingFunction`的計算結果關聯到`key`上,如果計算結果為`null`,則在`Map`中刪除`key`的對映. 要實現上述`merge()`方法中錯誤資訊拼接的例子,使用`compute()`程式碼如下: ```java map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg)); ``` #### computeIfAbsent() 該方法簽名為`V computeIfAbsent(K key, Function mappingFunction)`,作用是:只有在當前`Map`中**不存在`key`值的對映或對映值為`null`時**,才呼叫`mappingFunction`,並在`mappingFunction`執行結果非`null`時,將結果跟`key`關聯. `Function`是一個函式介面,裡面有一個待實現方法`R apply(T t)`. `computeIfAbsent()`常用來對`Map`的某個`key`值建立初始化對映.比如我們要實現一個多值對映,`Map`的定義可能是`Map>`,要向`Map`中放入新值,可通過如下程式碼實現: ```java Map> map = new HashMap<>(); // Java7及以前的實現方式 if(map.containsKey(1)){ map.get(1).add("one"); }else{ Set valueSet = new HashSet(); valueSet.add("one"); map.put(1, valueSet); } // Java8的實現方式 map.computeIfAbsent(1, v -> new HashSet()).add("yi"); ``` 使用`computeIfAbsent()`將條件判斷和新增操作合二為一,使程式碼更加簡潔. #### computeIfPresent() 該方法簽名為`V computeIfPresent(K key, BiFunction remappingFunction)`,作用跟`computeIfAbsent()`相反,即,只有在當前`Map`中**存在`key`值的對映且非`null`時**,才呼叫`remappingFunction`,如果`remappingFunction`執行結果為`null`,則刪除`key`的對映,否則使用該結果替換`key`原來的對映. 這個函式的功能跟如下程式碼是等效的: ```java // Java7及以前跟computeIfPresent()等效的程式碼 if (map.get(key) != null) { V oldValue = map.get(key); V newValue = remappingFunction.apply(key, oldValue); if (newValue != null) map.put(key, newValue); else map.remove(key); return newValue; } return null; ``` 1. Java8為容器新增一些有用的方法,這些方法有些是為**完善原有功能**,有些是為**引入函數語言程式設計**,學習和使用這些方法有助於我們寫出更加簡潔有效的程式碼. 2. **函式介面**雖然很多,但絕大多數時候我們根本不需要知道它們的名字,書寫Lambda表示式時型別推斷幫我們做了一切. 參考:https://github.com/CarpenterLee/JavaLambdaI