<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Lambda表達(dá)式你會(huì)用嗎?

          共 4957字,需瀏覽 10分鐘

           ·

          2021-01-21 09:28

          點(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ì)列舉了這些方法。

          接口名Java8新加入的方法
          CollectionremoveIf() spliterator() stream() parallelStream() forEach()
          ListreplaceAll() sort()
          MapgetOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

          這些新加入的方法大部分要用到java.util.function包下的接口,這意味著這些方法大部分都跟Lambda表達(dá)式相關(guān)。我們將逐一學(xué)習(xí)這些方法。


          6.1Collection中的新方法


          如上所示,接口CollectionList新加入了一些方法,我們以是List的子類ArrayList為例來說明。了解Java7ArrayList實(shí)現(xiàn)原理,將有助于理解下文。


          forEach()

          該方法的簽名為void forEach(Consumer 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 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 operator),作用是對每個(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 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 spliterator(),該方法返回容器的可拆分迭代器。從名字來看該方法跟iterator()方法有點(diǎn)像,我們知道Iterator是用來迭代容器的,Spliterator也有類似作用,但二者有如下不同:

          1. Spliterator既可以像Iterator那樣逐個(gè)迭代,也可以批量迭代。批量迭代可以降低迭代的開銷。

          2. Spliterator是可拆分的,一個(gè)Spliterator可以通過調(diào)用Spliterator trySplit()方法來嘗試分成兩個(gè)。一個(gè)是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 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)前Mapkey正好映射到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)前Mapkey的映射存在時(shí)才用value去替換原來的值,否則什么也不做.

          • replace(K key, V oldValue, V newValue),只有在當(dāng)前Mapkey的映射存在且等于oldValue時(shí)才用newValue去替換原來的值,否則什么也不做.


          replaceAll()

          該方法簽名為replaceAll(BiFunction 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 remappingFunction),作用是:

          1. 如果Mapkey對應(yīng)的映射不存在或者為null,則將value(不能是null)關(guān)聯(lián)到key上;

          2. 否則執(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ò)誤信息拼接到原來的信息上,比如:

          map.merge(key, newMsg, (v1, v2) -> v1+v2);

          compute()

          該方法簽名為compute(K key, BiFunction remappingFunction),作用是把remappingFunction的計(jì)算結(jié)果關(guān)聯(lián)到key上,如果計(jì)算結(jié)果為null,則在Map中刪除key的映射.

          要實(shí)現(xiàn)上述merge()方法中錯(cuò)誤信息拼接的例子,使用compute()代碼如下:

          map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));

          computeIfAbsent()

          該方法簽名為V computeIfAbsent(K key, Function 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 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;

          1. Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數(shù)式編程,學(xué)習(xí)和使用這些方法有助于我們寫出更加簡潔有效的代碼.

          2. 函數(shù)接口雖然很多,但絕大多數(shù)時(shí)候我們根本不需要知道它們的名字,書寫Lambda表達(dá)式時(shí)類型推斷幫我們做了一切.





          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼?2 秒


          感謝點(diǎn)贊支持下哈?

          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  可以看操逼的网站 | 成人黄色性生活视频 | 日本黄色日批视频网站 | 大鸡巴操嫩逼 | 草逼网视频|