Java8 Lambda表達(dá)式與Stream API (一):Lambda表達(dá)式

你要知道的Java8 匿名內(nèi)部類、函數(shù)式接口、lambda表達(dá)式與Stream API都在這里

轉(zhuǎn)載請注明出處 http://m.itdecent.cn/p/c832d2c6dd81

本文主要講解Java8 Stream API,但是要講解這一部分需要匿名內(nèi)部類、lambda表達(dá)式以及函數(shù)式接口的相關(guān)知識,本文將分為兩篇文章來講解上述內(nèi)容,讀者可以按需查閱。

本文是該系列博文的第一篇Java 匿名內(nèi)部類、lambda表達(dá)式與函數(shù)式接口,主要講解Java匿名內(nèi)部類、lambda表達(dá)式以及函數(shù)式接口,第二篇文章Java Stream API主要講解Java Stream API

匿名內(nèi)部類

匿名內(nèi)部類適用于那些只需要使用一次的類,比如設(shè)計模式下的命令模式,往往通過定義一系列接口進(jìn)行調(diào)用,有時有的命令只會執(zhí)行一次就不再執(zhí)行,這個時候如果單獨定義一個類就顯得過于復(fù)雜并且編譯會生成這個類的.class文件,不利于管理,這種情況下使用匿名內(nèi)部類就能夠很好的簡化編程并且不會編譯生成單獨的.class文件。

接下來看一個例子:

interface Programmer
{
    void listLanguages();
    void introduceMyself();
}

class MyProgrammer implements Programmer
{
    public void listLanguages() {
        System.out.println("Objective-C Swift Python Go Java");
    }

    public void introduceMyself() {
        System.out.println("My Name is Jiaming Chen");
    }
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        info(new MyProgrammer());
    }
}

上面這個例子為了執(zhí)行info函數(shù)定義了一個實現(xiàn)了Programmer接口的類MyProgrammer,如果它只執(zhí)行一次這樣就顯得過于復(fù)雜,如果采用匿名內(nèi)部類就會在很大程度上簡化編程,首先介紹一下匿名內(nèi)部類的基礎(chǔ)語法:

new 需要實現(xiàn)的接口() | 父類構(gòu)造器()
{
    //需要實現(xiàn)的方法或重載父類的方法
}

匿名內(nèi)部類的語法很簡單,必須要實現(xiàn)一個接口或者繼承一個類,可以看到使用了new關(guān)鍵詞,因此在創(chuàng)建匿名內(nèi)部類的同時會創(chuàng)建一個該類的實例,并且只能創(chuàng)建一個實例,創(chuàng)建完成后這個匿名內(nèi)部類就不能再使用,因此,匿名內(nèi)部類不能是抽象類,由于匿名內(nèi)部類沒有類名所以也不能定義構(gòu)造函數(shù),但是可以在定義匿名內(nèi)部類的時候調(diào)用父類的有參構(gòu)造器也可以定義初始化塊用于初始化父類的成員變量。下面這個栗子是將上述代碼修改為匿名內(nèi)部類的實現(xiàn)方式:

class MyProgrammer implements Programmer
{
    public void listLanguages() {
        System.out.println("Objective-C Swift Python Go Java");
    }

    public void introduceMyself() {
        System.out.println("My Name is Jiaming Chen");
    }
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        info(new Programmer(){
            public void listLanguages() {
                System.out.println("Objective-C Swift Python Go Java");
            }

            public void introduceMyself() {
                System.out.println("My Name is Jiaming Chen");
            }
        });
    }
}

通過對比發(fā)現(xiàn),使用匿名內(nèi)部類比重新定義一個新類更加簡潔,在創(chuàng)建匿名內(nèi)部類的時候可以調(diào)用父類的有參構(gòu)造函數(shù),栗子如下:

abstract class Programmer
{
    protected String name;
    
    public Programmer(String name)
    {
        this.name = name;
    }
    
    public abstract void listLanguages();
    public abstract void introduceMyself();
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        int age = 22;
        Programmer p = new Programmer("Jiaming Chen"){
            public void listLanguages()
            {
                System.out.println("Objective-C Swift Python Go Java");
            }
            public void introduceMyself()
            {
                System.out.println("My Name is " + this.name + " and I'm " + age + " years old.");
                //age = 2;
            }
        };
        info(p);
        //age = 23;
    }
}

上述栗子首先定義了一個抽象父類,并且該抽象父類只有一個構(gòu)造函數(shù),因此在創(chuàng)建匿名內(nèi)部類的時候需要顯示調(diào)用該構(gòu)造函數(shù),這樣就可以在匿名內(nèi)部類內(nèi)部使用父類定義的成員變量了,匿名內(nèi)部類也可以使用外部變量,在Java8中上述栗子中的age會自動聲明為final類型,這稱為effectively final,只要匿名內(nèi)部類訪問了一個局部變量,這個局部變量無論是否被final修飾它都會自動被聲明為final類型,不允許任何地方進(jìn)行修改,Java與其他語言相比在閉包內(nèi)訪問外部變量的局限更大,因為只能是final類型,比如OCblock內(nèi)部也可以捕獲外部變量,swift也有一套閉包值捕獲機(jī)制,都可以對捕獲的值進(jìn)行修改或權(quán)限的設(shè)置,Java的局限性太大。

lambda表達(dá)式

Java8引入了lambda表達(dá)式,在其他語言中,比如python、swift都支持lambda表達(dá)式,這個特性用起來也非常方便和簡潔。接下來舉一個常見的對一個列表進(jìn)行排序的例子:

class MyComparator implements Comparator<String>
{
    public int compare(String o1, String o2)
    {
        return o1.compareTo(o2);
    }
}

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort(new MyComparator());
        list.forEach(System.out::println);
        /*
        輸出:(下面的代碼的輸出結(jié)果是一致的,不再贅述)
        Golang
        Java
        Objective-C
        Python
        Swift
        */
    }
}

在開發(fā)中經(jīng)常會對一個集合類型進(jìn)行排序操作,調(diào)用集合的sort方法時需要傳入一個實現(xiàn)了Comparator接口的參數(shù),因此上述栗子就定義了一個類MyComparator并且這個類實現(xiàn)了Comparator接口,因此可以創(chuàng)建一個MyComparator的對象用于排序操作,不難發(fā)現(xiàn)這樣做非常復(fù)雜需要重新定義一個全新的類,經(jīng)過前文的介紹這里完全可以用匿名內(nèi)部類來代替,關(guān)于最后一行代碼list.forEach(System.out::println);在后文會介紹,這里先賣個關(guān)子,它的作用就是遍歷整個集合并輸出。接下來看一下使用匿名內(nèi)部類實現(xiàn)的方式:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort(new Comparator<String>(){
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }           
        });
        list.forEach(System.out::println);
    }
}

結(jié)果同上,顯而易見,采用了匿名內(nèi)部類更加的方便了,代碼簡潔明了,那有沒有再簡介一點的辦法呢?答案當(dāng)然是肯定的,那就是使用lambda表達(dá)式,栗子如下:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort((String o1, String o2)->{
            return o1.compareTo(o2);
        });
        list.forEach(System.out::println);
    }
}

通過上述代碼發(fā)現(xiàn),sort函數(shù)傳入的參數(shù)是一個lambda表達(dá)式,整個代碼非常類似前文中我們實現(xiàn)的compare函數(shù),但是我們不需要再寫new Comparator<String()這樣繁瑣的代碼,也不需要寫函數(shù)名,因此lambda表達(dá)式可以很好的替代匿名內(nèi)部類,不難發(fā)現(xiàn)lambda表達(dá)式由三部分組成:

  • 首先是參數(shù)列表,這個參數(shù)列表與要實現(xiàn)的方法的參數(shù)列表一致,因為系統(tǒng)已經(jīng)知道了你需要實現(xiàn)哪一個方法因此可以省略形參類型,當(dāng)只有一個參數(shù)時也可以省略圓括號,但是當(dāng)沒有形參時圓括號不可以省略
  • 接下來是一個->符號,該符號用于分隔形參列表與函數(shù)體,該符號不允許省略。
  • 最后就是代碼體了,如果代碼體只有一行代碼就可以省略掉花括號,并且如果方法需要有返回值連return關(guān)鍵詞都可以省略,系統(tǒng)會自動將這一行代碼的結(jié)果返回。

通過上面的講解,我們就可以寫一個更加簡潔lambda表達(dá)式了,栗子如下:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort((s1, s2)->s1.compareTo(s2));
        list.forEach(System.out::println);
    }
}

上面的代碼我們省略了形參的類型,由于只有一行我們同時省略了花括號和return語句,整個代碼相比使用匿名內(nèi)部類更加簡潔了。到這里有同學(xué)可能會問了,lambda表達(dá)式是怎么知道我們實現(xiàn)的是接口的哪一個方法?

lambda表達(dá)式的類型也被稱為目標(biāo)類型 target type,該類型必須是函數(shù)式接口 Functional Interface,函數(shù)式接口代表有且只有一個抽象方法,但是可以包含多個默認(rèn)方法或類方法的接口,因此使用lambda表達(dá)式系統(tǒng)一定知道我們實現(xiàn)的接口的哪一個方法,因為實現(xiàn)的接口有且只有一個抽象方法供我們實現(xiàn)。

函數(shù)式接口可以使用注釋@FunctionalInterface來要求編譯器在編譯時進(jìn)行檢查,是否只包含一個抽象方法。Java提供了大量的函數(shù)式接口這樣就能使用lambda表達(dá)式簡化編程。lambda表達(dá)式的目標(biāo)類型必須是函數(shù)式接口lambda表達(dá)式也只能為函數(shù)式接口創(chuàng)建對象因為lambda表達(dá)式只能實現(xiàn)一個抽象方法。

前文介紹了在使用lambda表達(dá)式時,如果代碼體只有一行代碼可以省略花括號,如果有返回值也可以省略return關(guān)鍵詞,不僅如此,lambda表達(dá)式在只有一條代碼時還可以引用其他方法或構(gòu)造器并自動調(diào)用,可以省略參數(shù)傳遞,代碼更加簡潔,引用方法的語法需要使用::符號。lambda表達(dá)式提供了四種引用方法和構(gòu)造器的方式:

  • 引用對象的方法 類::實例方法
  • 引用類方法 類::類方法
  • 引用特定對象的方法 特定對象::實例方法
  • 引用類的構(gòu)造器 類::new

舉個栗子:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        //list.sort((s1, s2)->s1.compareTo(s2));
        list.sort(String::compareTo);
        list.forEach(System.out::println);
    }
}

對比上述兩行代碼,第一個sort函數(shù)傳入了一個lambda表達(dá)式用于實現(xiàn)Comparator接口的compare函數(shù),由于該實現(xiàn)只有一條代碼,因此可以省略花括號以及return關(guān)鍵字。第二個sort方法則直接引用了對象的實例方法,語法規(guī)則為類::實例方法,系統(tǒng)會自動將函數(shù)式接口實現(xiàn)的方法的所有參數(shù)中的第一個參數(shù)作為調(diào)用者,接下來的參數(shù)依次傳入引用的方法中即自動進(jìn)行s1.compareTo(s2)的方法調(diào)用,明顯第二個sort函數(shù)調(diào)用更加簡潔明了。

最后一行代碼list.forEach(System.out::println);則引用了類方法,集合類的實例方法forEach接收一個Consumer接口對象,該接口是一個函數(shù)式接口,只有一個抽象方法void accept(T t);,因此可以使用lambda表達(dá)式進(jìn)行調(diào)用,這里引用System.out的類方法println,引用語法類::類方法,系統(tǒng)會自動將實現(xiàn)的函數(shù)式接口方法中的所有參數(shù)都傳入該類方法并進(jìn)行自動調(diào)用。

再舉一個栗子:

@FunctionalInterface
interface Index
{
    int index(String subString);
}

@FunctionalInterface
interface Generator
{
    String generate();
}

public class HelloWorld
{   
    static int getIndex(Index t, String subString)
    {
        return t.index(subString);
    }
    
    static String generateString(Generator g)
    {
        return g.generate();
    }
    
    public static void main(String[] args)
    {   
        String str = "Hello World";
        System.out.println(getIndex(str::indexOf, "e"));
        System.out.println(generateString(String::new).length());
    }
}

這個栗子似乎沒有任何實際意義,就是為了演示引用特定對象的實例方法和引用類的構(gòu)造器。接口IndexGenerator都是函數(shù)式接口,因此可以使用lambda表達(dá)式。對于getIndex方法需要傳入一個實現(xiàn)Index接口的對象和一個子串,在調(diào)用時首先定義了一個字符串Hello World,然后引用了這個對象的實例方法indexOf,這個時候系統(tǒng)會自動將這個特定對象作為調(diào)用者然后將所有的參數(shù)因此傳入該實力方法。
引用構(gòu)造器的方法也很簡單類::new,不再贅述。

總結(jié)

本文主要講解匿名內(nèi)部類lambda表達(dá)式、函數(shù)式接口以及lambda表達(dá)式方法引用構(gòu)造器引用,通過幾個例子逐步深入,逐步簡化代碼的編寫,可以發(fā)現(xiàn)Java8提供的lambda表達(dá)式是如此的強(qiáng)大。接下來的一篇文章會對Java8新增的Stream API進(jìn)行講解,Stream的流式API支持并行,對傳統(tǒng)編程方式進(jìn)行了改進(jìn),可以編寫出更簡潔明了的高性能代碼。有興趣的讀者可以閱讀Java Stream API。

備注

由于作者水平有限,難免出現(xiàn)紕漏,如有問題還請不吝賜教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡介 概念 Lambda 表達(dá)式可以理解為簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表、函數(shù)主...
    劉滌生閱讀 3,365評論 5 18
  • lambda表達(dá)式(又被成為“閉包”或“匿名方法”)方法引用和構(gòu)造方法引用擴(kuò)展的目標(biāo)類型和類型推導(dǎo)接口中的默認(rèn)方法...
    183207efd207閱讀 1,553評論 0 5
  • 注:之前關(guān)于Java8的認(rèn)知一直停留在知道有哪些修改和新的API上,對Lambda的認(rèn)識也是僅僅限于對匿名內(nèi)部類的...
    mualex閱讀 2,944評論 1 4
  • 原文鏈接: Lambdas 原文作者: shekhargulati 譯者: leege100 lambda表達(dá)式是...
    忽來閱讀 6,771評論 8 129
  • 對于大數(shù)據(jù)時代為我們創(chuàng)造的各種便利,我是很感激的。我有一個習(xí)慣,每周末都會定期刪除收藏夾,保證我打開第一次以后還想...
    落花成琳閱讀 399評論 2 2

友情鏈接更多精彩內(nèi)容