<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    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ù)式編程(請(qǐng)注意多了一個(gè)“式”字)——Functional Programming,雖然也可以歸結(jié)到面向過程的程序設(shè)計(jì),但其思想更接近數(shù)學(xué)計(jì)算。

    我們首先要搞明白計(jì)算機(jī)(Computer)和計(jì)算(Compute)的概念。在計(jì)算機(jī)的層次上,CPU執(zhí)行的是加減乘除的指令代碼,以及各種條件判斷和跳轉(zhuǎn)指令,所以,匯編語(yǔ)言是最貼近計(jì)算機(jī)的語(yǔ)言。而計(jì)算則指數(shù)學(xué)意義上的計(jì)算,越是抽象的計(jì)算,離計(jì)算機(jī)硬件越遠(yuǎn)。對(duì)應(yīng)到編程語(yǔ)言,就是越低級(jí)的語(yǔ)言,越貼近計(jì)算機(jī),抽象程度低,執(zhí)行效率高,比如C語(yǔ)言;越高級(jí)的語(yǔ)言,越貼近計(jì)算,抽象程度高,執(zhí)行效率低,比如Lisp語(yǔ)言。

    函數(shù)式編程就是一種抽象程度很高的編程范式,純粹的函數(shù)式編程語(yǔ)言編寫的函數(shù)沒有變量,因此,任意一個(gè)函數(shù),只要輸入是確定的,輸出就是確定的,這種純函數(shù)我們稱之為沒有副作用。而允許使用變量的程序設(shè)計(jì)語(yǔ)言,由于函數(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ù)式編程稱為L(zhǎng)ambda計(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)建線程,省去了取名字的煩惱,但是還能不能再簡(jiǎn)化一些呢?

    JDK1.8 Lambda表達(dá)式寫法

    Thread?thread?=?new?Thread(()?->?System.out.println("Thread?Run"));?//一行搞定

    thread.start();


    我們可以看到Lambda一行代碼就完成了線程的創(chuàng)建,簡(jiǎn)直不要太方便。(至于Lambda表達(dá)式的語(yǔ)法,我們下面章節(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á)式語(yǔ)法

    Lambda 表達(dá)式,也可稱為閉包,它是推動(dòng) Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。

    使用 Lambda 表達(dá)式可以使代碼變的更加簡(jiǎn)潔緊湊。上一章節(jié)我們已經(jīng)見識(shí)到了Lambda表達(dá)式的優(yōu)點(diǎn),那么Lambda表達(dá)式到底該怎么寫呢?


    3.1語(yǔ)法

    lambda 表達(dá)式的語(yǔ)法格式如下:

    (parameters)?->?expression???//一行代碼
    ??或
    (parameters)?->{?statements;?}??//多行代碼

    lambda表達(dá)式的重要特征:

    • 可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識(shí)別參數(shù)值。

    • 可選的參數(shù)圓括號(hào):一個(gè)參數(shù)無(wú)需定義圓括號(hào),但多個(gè)參數(shù)需要定義圓括號(hào)。

    • 可選的大括號(hào):如果主體包含了一個(gè)語(yǔ)句,就不需要使用大括號(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?對(duì)象,并在控制臺(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)過上面幾部分的介紹,相信大家對(duì)Lambda表達(dá)式已經(jīng)有了初步認(rèn)識(shí),學(xué)會(huì)了如何使用。但想必大家心中始終有一個(gè)疑問,Lambda表達(dá)式似乎只是為了簡(jiǎn)化匿名內(nèi)部類的寫法,其他也沒啥區(qū)別了。這看起來僅僅通過語(yǔ)法糖在編譯階段把所有的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á)式對(duì)接。

    首先回顧一下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),作用是對(duì)容器中的每個(gè)元素執(zhí)行action指定的動(dòng)作,其中Consumer是個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)方法void accept(T t)(后面我們會(huì)看到,這個(gè)方法叫什么根本不重要,你甚至不需要記憶它的名字)。

    需求:假設(shè)有一個(gè)字符串列表,需要打印出其中所有長(zhǎng)度大于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除其中所有長(zhǎng)度大于3的字符串。

    我們知道如果需要在迭代過程沖對(duì)容器進(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)?//?刪除長(zhǎng)度大于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(){?//?刪除長(zhǎng)度大于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);?//?刪除長(zhǎng)度大于3的元素


    使用Lambda表達(dá)式不需要記憶Predicate接口名,也不需要記憶test()方法名,只需要知道此處需要一個(gè)返回布爾類型的Lambda表達(dá)式就行了。


    replaceAll()

    該方法簽名為void replaceAll(UnaryOperator operator),作用是對(duì)每個(gè)元素執(zhí)行operator指定的操作,并用操作結(jié)果來替換原來的元素。其中UnaryOperator是一個(gè)函數(shù)接口,里面只有一個(gè)待實(shí)現(xiàn)函數(shù)T apply(T t)。

    需求:假設(shè)有一個(gè)字符串列表,將其中所有長(zhǎng)度大于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接口。我們知道可以用更為簡(jiǎn)潔的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ī)則對(duì)容器元素進(jìn)行排序。Comparator接口我們并不陌生,其中有一個(gè)方法int compare(T o1, T o2)需要實(shí)現(xiàn),顯然該接口是個(gè)函數(shù)接口。

    需求:假設(shè)有一個(gè)字符串列表,按照字符串長(zhǎng)度增序?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),作用是對(duì)Map中的每個(gè)映射執(zhí)行action指定的操作,其中BiConsumer是一個(gè)函數(shù)接口,里面有一個(gè)待實(shí)現(xiàn)方法void accept(T t, U u)。BinConsumer接口名字和accept()方法名字都不重要,請(qǐng)不要記憶他們。

    需求:假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map,請(qǐng)輸出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í)際場(chǎng)景中沒人使用匿名內(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中對(duì)應(yīng)的value,如果沒有找到則返回defaultValue。使用該方法程序員可以省去查詢指定鍵值是否存在的麻煩.

    需求;假設(shè)有一個(gè)數(shù)字到對(duì)應(yīng)英文單詞的Map,輸出4對(duì)應(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中,否則不對(duì)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),作用是對(duì)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ù)字到對(duì)應(yīng)英文單詞的Map,請(qǐng)將原來映射關(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());

    簡(jiǎn)潔到讓人難以置信.


    merge()

    該方法簽名為merge(K key, V value, BiFunction remappingFunction),作用是:

    1. 如果Mapkey對(duì)應(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()方法雖然語(yǔ)義有些復(fù)雜,但該方法的用方式很明確,一個(gè)比較常見的場(chǎng)景是將新的錯(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()常用來對(duì)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()將條件判斷和添加操作合二為一,使代碼更加簡(jiǎn)潔.


    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í)和使用這些方法有助于我們寫出更加簡(jiǎn)潔有效的代碼.

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





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

    ??????

    ??長(zhǎng)按上方微信二維碼?2 秒


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

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

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    日韩99在线 | 国产激情原创 | 日本免费内射 | 国产操网| 免费作爱视频 |