Lambda表達(dá)式你會(huì)用嗎?
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|? 說故事的五公子
來源 |? urlify.cn/ey67Jn
76套java從入門到精通實(shí)戰(zhàn)課程分享
1、函數(shù)式編程
在正式學(xué)習(xí)Lambda之前,我們先來了解一下什么是函數(shù)式編程
我們先看看什么是函數(shù)。函數(shù)是一種最基本的任務(wù),一個(gè)大型程序就是一個(gè)頂層函數(shù)調(diào)用若干底層函數(shù),這些被調(diào)用的函數(shù)又可以調(diào)用其他函數(shù),即大任務(wù)被一層層拆解并執(zhí)行。所以函數(shù)就是面向過程的程序設(shè)計(jì)的基本單元。
Java不支持單獨(dú)定義函數(shù),但可以把靜態(tài)方法視為獨(dú)立的函數(shù),把實(shí)例方法視為自帶this參數(shù)的函數(shù)。而函數(shù)式編程(請注意多了一個(gè)“式”字)——Functional Programming,雖然也可以歸結(jié)到面向過程的程序設(shè)計(jì),但其思想更接近數(shù)學(xué)計(jì)算。
我們首先要搞明白計(jì)算機(jī)(Computer)和計(jì)算(Compute)的概念。在計(jì)算機(jī)的層次上,CPU執(zhí)行的是加減乘除的指令代碼,以及各種條件判斷和跳轉(zhuǎn)指令,所以,匯編語言是最貼近計(jì)算機(jī)的語言。而計(jì)算則指數(shù)學(xué)意義上的計(jì)算,越是抽象的計(jì)算,離計(jì)算機(jī)硬件越遠(yuǎn)。對應(yīng)到編程語言,就是越低級(jí)的語言,越貼近計(jì)算機(jī),抽象程度低,執(zhí)行效率高,比如C語言;越高級(jí)的語言,越貼近計(jì)算,抽象程度高,執(zhí)行效率低,比如Lisp語言。
函數(shù)式編程就是一種抽象程度很高的編程范式,純粹的函數(shù)式編程語言編寫的函數(shù)沒有變量,因此,任意一個(gè)函數(shù),只要輸入是確定的,輸出就是確定的,這種純函數(shù)我們稱之為沒有副作用。而允許使用變量的程序設(shè)計(jì)語言,由于函數(shù)內(nèi)部的變量狀態(tài)不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數(shù)是有副作用的。
函數(shù)式編程的一個(gè)特點(diǎn)就是,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù),還允許返回一個(gè)函數(shù)!
函數(shù)式編程最早是數(shù)學(xué)家阿隆佐·邱奇研究的一套函數(shù)變換邏輯,又稱Lambda Calculus(λ-Calculus),所以也經(jīng)常把函數(shù)式編程稱為Lambda計(jì)算。
Java平臺(tái)從Java 8開始,支持函數(shù)式編程。
2、Lambda初體驗(yàn)
先從一個(gè)例子開始,讓我們來看一下Lambda可以用在什么地方。
2.1例一:創(chuàng)建線程
常見創(chuàng)建線程的方法(JDK1.8以前)
//JDK1.7通過匿名內(nèi)部類的方式創(chuàng)建線程
Thread?thread?=?new?Thread(new?Runnable()?{
????@Override
????public?void?run()?{?//實(shí)現(xiàn)run方法
????????System.out.println("Thread?Run...");
????}
});
thread.start();
通過匿名內(nèi)部類的方式創(chuàng)建線程,省去了取名字的煩惱,但是還能不能再簡化一些呢?
JDK1.8 Lambda表達(dá)式寫法
Thread?thread?=?new?Thread(()?->?System.out.println("Thread?Run"));?//一行搞定
thread.start();
我們可以看到Lambda一行代碼就完成了線程的創(chuàng)建,簡直不要太方便。(至于Lambda表達(dá)式的語法,我們下面章節(jié)再詳細(xì)介紹)
如果你的邏輯不止一行代碼,那么你還可以這么寫
Thread?thread?=?new?Thread(()?->?{
????System.out.println("Thread?Run");
????System.out.println("Hello");
});
thread.start();
用{}將代碼塊包裹起來
2.2例二:自定義比較器
我們先來看一下JDK1.7是如何實(shí)現(xiàn)自定義比較器的
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方法傳入了兩個(gè)參數(shù),一個(gè)是待排序的list,一個(gè)是比較器(排序規(guī)則),這里也是通過匿名內(nèi)部類的方式實(shí)現(xiàn)的比較器。
下面我們來看一下Lambda表達(dá)式如何實(shí)現(xiàn)比較器?
List?list?=?Arrays.asList("Hi",?"Life",?"Hello~",?"World");
Collections.sort(list,?(s1,?s2)?->{//?省略了參數(shù)的類型,編譯器會(huì)根據(jù)上下文信息自動(dòng)推斷出類型
????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表達(dá)式和匿名內(nèi)部類的作用相同,但是省略了很多代碼,可以大大加快開發(fā)速度
3、Lambda表達(dá)式語法
Lambda 表達(dá)式,也可稱為閉包,它是推動(dòng) Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。
使用 Lambda 表達(dá)式可以使代碼變的更加簡潔緊湊。上一章節(jié)我們已經(jīng)見識(shí)到了Lambda表達(dá)式的優(yōu)點(diǎn),那么Lambda表達(dá)式到底該怎么寫呢?
3.1語法
lambda 表達(dá)式的語法格式如下:
(parameters)?->?expression???//一行代碼
??或
(parameters)?->{?statements;?}??//多行代碼
lambda表達(dá)式的重要特征:
可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識(shí)別參數(shù)值。
可選的參數(shù)圓括號(hào):一個(gè)參數(shù)無需定義圓括號(hào),但多個(gè)參數(shù)需要定義圓括號(hào)。
可選的大括號(hào):如果主體包含了一個(gè)語句,就不需要使用大括號(hào)。
可選的返回關(guān)鍵字:如果主體只有一個(gè)表達(dá)式返回值則編譯器會(huì)自動(dòng)返回值,大括號(hào)需要指定明表達(dá)式返回了一個(gè)數(shù)值。
//?1.?不需要參數(shù),返回值為?5??
()?->?5??
??
//?2.?接收一個(gè)參數(shù)(數(shù)字類型),返回其2倍的值??
x?->?2?*?x??
??
//?3.?接受2個(gè)參數(shù)(數(shù)字),并返回他們的差值??
(x,?y)?->?x?–?y??
??
//?4.?接收2個(gè)int型整數(shù),返回他們的和??
(int?x,?int?y)?->?x?+?y??
??
//?5.?接受一個(gè)?string?對象,并在控制臺(tái)打印,不返回任何值(看起來像是返回void)??
(String?s)?->?System.out.print(s)
4、函數(shù)接口
上面幾個(gè)章節(jié)給大家介紹Lambda表達(dá)式的基本使用,那么是不是在任意地方都可以使用Lambda表達(dá)式呢?
其實(shí)Lambda表達(dá)式使用是有限制的。也許你已經(jīng)想到了,能夠使用Lambda的依據(jù)是必須有相應(yīng)的函數(shù)接口。(函數(shù)接口,是指內(nèi)部只有一個(gè)抽象方法的接口)
4.1自定義函數(shù)接口
自定義函數(shù)接口很容易,只需要編寫一個(gè)只有一個(gè)抽象方法的接口即可。
//?自定義函數(shù)接口
@FunctionalInterface
public?interface?PersonInterface{
????void?accept(T?t);
}
上面代碼中的@FunctionalInterface是可選的,但加上該標(biāo)注編譯器會(huì)幫你檢查接口是否符合函數(shù)接口規(guī)范。就像加入@Override標(biāo)注會(huì)檢查是否重載了函數(shù)一樣。
那么根據(jù)上面的自定義函數(shù)式接口,我們就可以寫出如下的Lambda表達(dá)式。
PersonInterface?p?=?str?->?System.out.println(str);
5、Lambda和匿名內(nèi)部類
經(jīng)過上面幾部分的介紹,相信大家對Lambda表達(dá)式已經(jīng)有了初步認(rèn)識(shí),學(xué)會(huì)了如何使用。但想必大家心中始終有一個(gè)疑問,Lambda表達(dá)式似乎只是為了簡化匿名內(nèi)部類的寫法,其他也沒啥區(qū)別了。這看起來僅僅通過語法糖在編譯階段把所有的Lambda表達(dá)式替換成匿名內(nèi)部類就可以了,事實(shí)真的如此嗎?
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????new?Thread(new?Runnable()?{
????????????@Override
????????????public?void?run()?{
????????????????System.out.println("Anonymous?class");
????????????}
????????}).start();
????}
}
匿名內(nèi)部類也是一個(gè)類,只不過我們不需要顯示為他定義名稱,但是編譯器會(huì)自動(dòng)為匿名內(nèi)部類命名。Main編輯后的文件如下圖

我們可以看到共有兩個(gè)class文件,一個(gè)是Main.class,而另一個(gè)則是編輯器為我們命名的內(nèi)部類。
下面我們來看一下Lambda表達(dá)式會(huì)產(chǎn)生幾個(gè)class文件
public?class?Main?{
????public?static?void?main(String[]?args)?{
????????new?Thread(()?->?System.out.println("Lambda")).start();
????}
}

Lambda表達(dá)式通過invokedynamic指令實(shí)現(xiàn),書寫Lambda表達(dá)式不會(huì)產(chǎn)生新的類
6、Lambda在集合中的運(yùn)用
既然Lambda表達(dá)式這么方便,那么哪些地方可以使用Lambda表達(dá)式呢?
我們先從最熟悉的Java集合框架(Java Collections Framework, JCF)開始說起。
為引入Lambda表達(dá)式,Java8新增了java.util.funcion包,里面包含常用的函數(shù)接口,這是Lambda表達(dá)式的基礎(chǔ),Java集合框架也新增部分接口,以便與Lambda表達(dá)式對接。
首先回顧一下Java集合框架的接口繼承結(jié)構(gòu):

上圖中綠色標(biāo)注的接口類,表示在Java8中加入了新的接口方法,當(dāng)然由于繼承關(guān)系,他們相應(yīng)的子類也都會(huì)繼承這些新方法。下表詳細(xì)列舉了這些方法。
| Collection | removeIf() spliterator() stream() parallelStream() forEach() |
| List | replaceAll() sort() |
| Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
這些新加入的方法大部分要用到java.util.function包下的接口,這意味著這些方法大部分都跟Lambda表達(dá)式相關(guān)。我們將逐一學(xué)習(xí)這些方法。
6.1Collection中的新方法
如上所示,接口Collection和List新加入了一些方法,我們以是List的子類ArrayList為例來說明。了解Java7ArrayList實(shí)現(xiàn)原理,將有助于理解下文。
forEach()
該方法的簽名為void forEach(Consumer super E> action),作用是對容器中的每個(gè)元素執(zhí)行action指定的動(dòng)作,其中Consumer是個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法void accept(T t)(后面我們會(huì)看到,這個(gè)方法叫什么根本不重要,你甚至不需要記憶它的名字)。
需求:假設(shè)有一個(gè)字符串列表,需要打印出其中所有長度大于3的字符串.
Java7及以前我們可以用增強(qiáng)的for循環(huán)實(shí)現(xiàn):
//?使用曾強(qiáng)for循環(huán)迭代
ArrayList?list?=?new?ArrayList<>(Arrays.asList("I",?"love",?"you",?"too"));
for(String?str?:?list){
????if(str.length()>3)
????????System.out.println(str);
}
現(xiàn)在使用forEach()方法結(jié)合匿名內(nèi)部類,可以這樣實(shí)現(xiàn):
//?使用forEach()結(jié)合匿名內(nèi)部類迭代
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);
????}
});
上述代碼調(diào)用forEach()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)Comsumer接口。到目前為止我們沒看到這種設(shè)計(jì)有什么好處,但是不要忘記Lambda表達(dá)式,使用Lambda表達(dá)式實(shí)現(xiàn)如下:
//?使用forEach()結(jié)合Lambda表達(dá)式迭代
ArrayList?list?=?new?ArrayList<>(Arrays.asList("I",?"love",?"you",?"too"));
list.forEach(?str?->?{
????????if(str.length()>3)
????????????System.out.println(str);
????});
上述代碼給forEach()方法傳入一個(gè)Lambda表達(dá)式,我們不需要知道accept()方法,也不需要知道Consumer接口,類型推導(dǎo)幫我們做了一切。
removeIf()
該方法簽名為boolean removeIf(Predicate super E> filter),作用是刪除容器中所有滿足filter指定條件的元素,其中Predicate是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法boolean test(T t),同樣的這個(gè)方法的名字根本不重要,因?yàn)橛玫臅r(shí)候不需要書寫這個(gè)名字。
需求:假設(shè)有一個(gè)字符串列表,需要?jiǎng)h除其中所有長度大于3的字符串。
我們知道如果需要在迭代過程沖對容器進(jìn)行刪除操作必須使用迭代器,否則會(huì)拋出ConcurrentModificationException,所以上述任務(wù)傳統(tǒng)的寫法是:
//?使用迭代器刪除列表元素
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();
}
現(xiàn)在使用removeIf()方法結(jié)合匿名內(nèi)部類,我們可是這樣實(shí)現(xiàn):
//?使用removeIf()結(jié)合匿名名內(nèi)部類實(shí)現(xiàn)
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()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)Precicate接口。相信你已經(jīng)想到用Lambda表達(dá)式該怎么寫了:
//?使用removeIf()結(jié)合Lambda表達(dá)式實(shí)現(xiàn)
ArrayList?list?=?new?ArrayList<>(Arrays.asList("I",?"love",?"you",?"too"));
list.removeIf(str?->?str.length()>3);?//?刪除長度大于3的元素
使用Lambda表達(dá)式不需要記憶Predicate接口名,也不需要記憶test()方法名,只需要知道此處需要一個(gè)返回布爾類型的Lambda表達(dá)式就行了。
replaceAll()
該方法簽名為void replaceAll(UnaryOperator,作用是對每個(gè)元素執(zhí)行operator指定的操作,并用操作結(jié)果來替換原來的元素。其中UnaryOperator是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)函數(shù)T apply(T t)。
需求:假設(shè)有一個(gè)字符串列表,將其中所有長度大于3的元素轉(zhuǎn)換成大寫,其余元素不變。
Java7及之前似乎沒有優(yōu)雅的辦法:
//?使用下標(biāo)實(shí)現(xiàn)元素替換
ArrayList?list?=?new?ArrayList<>(Arrays.asList("I",?"love",?"you",?"too"));
for(int?i=0;?i????String?str?=?list.get(i);
????if(str.length()>3)
????????list.set(i,?str.toUpperCase());
}
使用replaceAll()方法結(jié)合匿名內(nèi)部類可以實(shí)現(xiàn)如下:
//?使用匿名內(nèi)部類實(shí)現(xiàn)
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;
????}
});
上述代碼調(diào)用replaceAll()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)UnaryOperator接口。我們知道可以用更為簡潔的Lambda表達(dá)式實(shí)現(xiàn):
//?使用Lambda表達(dá)式實(shí)現(xiàn)
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 super E> c),該方法根據(jù)c指定的比較規(guī)則對容器元素進(jìn)行排序。Comparator接口我們并不陌生,其中有一個(gè)方法int compare(T o1, T o2)需要實(shí)現(xiàn),顯然該接口是個(gè)函數(shù)接口。
需求:假設(shè)有一個(gè)字符串列表,按照字符串長度增序?qū)υ嘏判颉?/em>
由于Java7以及之前sort()方法在Collections工具類中,所以代碼要這樣寫:
//?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();
????}
});
現(xiàn)在可以直接使用List.sort()方法,結(jié)合Lambda表達(dá)式,可以這樣寫:
//?List.sort()方法結(jié)合Lambda表達(dá)式
ArrayList?list?=?new?ArrayList<>(Arrays.asList("I",?"love",?"you",?"too"));
list.sort((str1,?str2)?->?str1.length()-str2.length());
spliterator()
方法簽名為Spliterator,該方法返回容器的可拆分迭代器。從名字來看該方法跟iterator()方法有點(diǎn)像,我們知道Iterator是用來迭代容器的,Spliterator也有類似作用,但二者有如下不同:
Spliterator既可以像Iterator那樣逐個(gè)迭代,也可以批量迭代。批量迭代可以降低迭代的開銷。Spliterator是可拆分的,一個(gè)Spliterator可以通過調(diào)用Spliterator方法來嘗試分成兩個(gè)。一個(gè)是trySplit() this,另一個(gè)是新返回的那個(gè),這兩個(gè)迭代器代表的元素沒有重疊。
可通過(多次)調(diào)用Spliterator.trySplit()方法來分解負(fù)載,以便多線程處理。
stream()和parallelStream()
stream()和parallelStream()分別返回該容器的Stream視圖表示,不同之處在于parallelStream()返回并行的Stream。Stream是Java函數(shù)式編程的核心類,我們會(huì)在后面章節(jié)中學(xué)習(xí)。
6.2Map中的新方法
相比Collection,Map中加入了更多的方法,我們以HashMap為例來逐一探秘。了解[Java7HashMap實(shí)現(xiàn)原理](https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/6-HashSet and HashMap.md),將有助于理解下文。
forEach()
該方法簽名為void forEach(BiConsumer super K,? super V> action),作用是對Map中的每個(gè)映射執(zhí)行action指定的操作,其中BiConsumer是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法void accept(T t, U u)。BinConsumer接口名字和accept()方法名字都不重要,請不要記憶他們。
需求:假設(shè)有一個(gè)數(shù)字到對應(yīng)英文單詞的Map,請輸出Map中的所有映射關(guān)系.
Java7以及之前經(jīng)典的代碼如下:
//?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()方法,結(jié)合匿名內(nèi)部類,代碼如下:
//?使用forEach()結(jié)合匿名內(nèi)部類迭代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);
????}
});
上述代碼調(diào)用forEach()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)BiConsumer接口。當(dāng)然,實(shí)際場景中沒人使用匿名內(nèi)部類寫法,因?yàn)橛蠰ambda表達(dá)式:
//?使用forEach()結(jié)合Lambda表達(dá)式迭代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表達(dá)式?jīng)]關(guān)系,但是很有用。方法簽名為V getOrDefault(Object key, V defaultValue),作用是按照給定的key查詢Map中對應(yīng)的value,如果沒有找到則返回defaultValue。使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩.
需求;假設(shè)有一個(gè)數(shù)字到對應(yīng)英文單詞的Map,輸出4對應(yīng)的英文單詞,如果不存在則輸出NoValue
//?查詢Map中指定的值,不存在時(shí)使用默認(rèn)值
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表達(dá)式?jīng)]關(guān)系,但是很有用。方法簽名為V putIfAbsent(K key, V value),作用是只有在不存在key值的映射或映射值為null時(shí),才將value指定的值放入到Map中,否則不對Map做更改.該方法將條件判斷和賦值合二為一,使用起來更加方便.
remove()
我們都知道Map中有一個(gè)remove(Object key)方法,來根據(jù)指定key值刪除Map中的映射關(guān)系;Java8新增了remove(Object key, Object value)方法,只有在當(dāng)前Map中key正好映射到value時(shí)才刪除該映射,否則什么也不做.
replace()
在Java7及以前,要想替換Map中的映射關(guān)系可通過put(K key, V value)方法實(shí)現(xiàn),該方法總是會(huì)用新值替換原來的值.為了更精確的控制替換行為,Java8在Map中加入了兩個(gè)replace()方法,分別如下:
replace(K key, V value),只有在當(dāng)前Map中key的映射存在時(shí)才用value去替換原來的值,否則什么也不做.replace(K key, V oldValue, V newValue),只有在當(dāng)前Map中key的映射存在且等于oldValue時(shí)才用newValue去替換原來的值,否則什么也不做.
replaceAll()
該方法簽名為replaceAll(BiFunction super K,? super V,? extends V> function),作用是對Map中的每個(gè)映射執(zhí)行function指定的操作,并用function的執(zhí)行結(jié)果替換原來的value,其中BiFunction是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法R apply(T t, U u).不要被如此多的函數(shù)接口嚇到,因?yàn)槭褂玫臅r(shí)候根本不需要知道他們的名字.
需求:假設(shè)有一個(gè)數(shù)字到對應(yīng)英文單詞的Map,請將原來映射關(guān)系中的單詞都轉(zhuǎn)換成大寫.
Java7以及之前經(jīng)典的代碼如下:
//?Java7以及之前替換所有Map中所有映射關(guān)系
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()方法結(jié)合匿名內(nèi)部類,實(shí)現(xiàn)如下:
//?使用replaceAll()結(jié)合匿名內(nèi)部類實(shí)現(xiàn)
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();
????}
});
上述代碼調(diào)用replaceAll()方法,并使用匿名內(nèi)部類實(shí)現(xiàn)BiFunction接口。更進(jìn)一步的,使用Lambda表達(dá)式實(shí)現(xiàn)如下:
//?使用replaceAll()結(jié)合Lambda表達(dá)式實(shí)現(xiàn)
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 super V,? super V,? extends V> remappingFunction),作用是:
如果
Map中key對應(yīng)的映射不存在或者為null,則將value(不能是null)關(guān)聯(lián)到key上;否則執(zhí)行
remappingFunction,如果執(zhí)行結(jié)果非null則用該結(jié)果跟key關(guān)聯(lián),否則在Map中刪除key的映射.
參數(shù)中BiFunction函數(shù)接口前面已經(jīng)介紹過,里面有一個(gè)待實(shí)現(xiàn)方法R apply(T t, U u).
merge()方法雖然語義有些復(fù)雜,但該方法的用方式很明確,一個(gè)比較常見的場景是將新的錯(cuò)誤信息拼接到原來的信息上,比如:
compute()
該方法簽名為compute(K key, BiFunction super K,? super V,? extends V> remappingFunction),作用是把remappingFunction的計(jì)算結(jié)果關(guān)聯(lián)到key上,如果計(jì)算結(jié)果為null,則在Map中刪除key的映射.
要實(shí)現(xiàn)上述merge()方法中錯(cuò)誤信息拼接的例子,使用compute()代碼如下:
computeIfAbsent()
該方法簽名為V computeIfAbsent(K key, Function super K,? extends V> mappingFunction),作用是:只有在當(dāng)前Map中不存在key值的映射或映射值為null時(shí),才調(diào)用mappingFunction,并在mappingFunction執(zhí)行結(jié)果非null時(shí),將結(jié)果跟key關(guān)聯(lián).
Function是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法R apply(T t).
computeIfAbsent()常用來對Map的某個(gè)key值建立初始化映射.比如我們要實(shí)現(xiàn)一個(gè)多值映射,Map的定義可能是Map,要向Map中放入新值,可通過如下代碼實(shí)現(xiàn):
Map>?map?=?new?HashMap<>();
//?Java7及以前的實(shí)現(xiàn)方式
if(map.containsKey(1)){
????map.get(1).add("one");
}else{
????Set?valueSet?=?new?HashSet();
????valueSet.add("one");
????map.put(1,?valueSet);
}
//?Java8的實(shí)現(xiàn)方式
map.computeIfAbsent(1,?v?->?new?HashSet()).add("yi");
使用computeIfAbsent()將條件判斷和添加操作合二為一,使代碼更加簡潔.
computeIfPresent()
該方法簽名為V computeIfPresent(K key, BiFunction super K,? super V,? extends V> remappingFunction),作用跟computeIfAbsent()相反,即,只有在當(dāng)前Map中存在key值的映射且非null時(shí),才調(diào)用remappingFunction,如果remappingFunction執(zhí)行結(jié)果為null,則刪除key的映射,否則使用該結(jié)果替換key原來的映射.
這個(gè)函數(shù)的功能跟如下代碼是等效的:
//?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;
Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數(shù)式編程,學(xué)習(xí)和使用這些方法有助于我們寫出更加簡潔有效的代碼.
函數(shù)接口雖然很多,但絕大多數(shù)時(shí)候我們根本不需要知道它們的名字,書寫Lambda表達(dá)式時(shí)類型推斷幫我們做了一切.
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼?2 秒
感謝點(diǎn)贊支持下哈?
