<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁 ::  :: 聯系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評論 :: 0 Trackbacks
    Java 8的語言變化
    --理解Lambda表達式和變化的接口類是如何使Java 8成為新的語言
    本文是IBM developerWorks中的一篇介紹Java 8關鍵新特性的文章,它主要關注Lambda表達式和改進的接口。(2014.04.19最后更新)

        Java 8包含了一組重要的新的語言特性,使你能夠更方便地構造程序。Lambda表達為內聯的代碼塊定義了一種新的語法,給予你與匿名內部類相同的靈活性,但又沒有那么多模板代碼。接口的改變使得能夠為已有接口加入新的特性,而不必打破現有代碼的兼容性。了解這些語言變化是怎樣一起工作的,請閱讀本系列另一篇文章"Java 8并發基礎",可以看到如何在Java 8流中使用Lambda。
        Java 8的最大改變就是增加了對Lambda表達式的支持。Lambda表達式一種通過引用進行傳遞的代碼塊。它類似于某些其它語言的閉包:代碼實現了一個功能,可以傳入一個或多個參數,還可以返回一個結果值。閉包被定義在一個上下文中,它可以訪問(在Lambda中是只讀訪問)上下文中的值。
        如果你不熟悉閉包,也不必擔心。Java 8的Lambda表達式是幾乎每個Java開發者都熟悉的匿名內部類的一個高效版規范。如果你只想在一個位置實現一個接口,或是創建一個基類的子類時,匿名內部類為此提供了一種內聯實現。Lambda表達式也用于相同的方式,但是它使用一種縮略的語法,使得這些實現比一個標準的內部類定義更為簡潔。
        在本文中,你將看到如何在不同的場景下使用Lambda表達式,并且你會學到與Java接口定義相關的擴展。在本文章的姊妹篇JVM并發系列的"Java 8并發基礎"一文中,可以看到更多使用Lambda表達式的例子,包括在Java 8流特性中的應用。

    進入Lambda
        Lambda表達式就是Java 8所稱的函數接口的實現:一個接口只定義一個抽象方法。只定義一個抽象方法的限制是非常重要的,因為Lambda表達式的語法并不會使用方法名。相反,該表達式會使用動態類型識別(匹配參數和返回類型,很多動態語言都這么做)去保證提供的Lambda能夠與期望的接口方法兼容。
        在清單1所示的簡單例子中,一個Lambda表達式被用來對Name實例進行排序。main()方法中的第一個代碼塊使用一個匿名內部類去實現Comparator<Name>接口,第二個語句塊則使用Lambda表達式。
    清單1. 比較Lambda表達式與匿名內部類
    public class Name {
        
    public final String firstName;
        
    public final String lastName;

        
    public Name(String first, String last) {
            firstName 
    = first;
            lastName 
    = last;
        }

        
    // only needed for chained comparator
        public String getFirstName() {
            
    return firstName;
        }

        
    // only needed for chained comparator
        public String getLastName() {
            
    return lastName;
        }

        
    // only needed for direct comparator (not for chained comparator)
        public int compareTo(Name other) {
            
    int diff = lastName.compareTo(other.lastName);
            
    if (diff == 0) {
                diff 
    = firstName.compareTo(other.firstName);
            }
            
    return diff;
        }
        
    }

    public class NameSort {
        
        
    private static final Name[] NAMES = new Name[] {
            
    new Name("Sally""Smith"),
            
        };
        
        
    private static void printNames(String caption, Name[] names) {
            
        }

        
    public static void main(String[] args) {

            
    // sort array using anonymous inner class
            Name[] copy = Arrays.copyOf(NAMES, NAMES.length);
            Arrays.sort(copy, 
    new Comparator<Name>() {
                @Override
                
    public int compare(Name a, Name b) {
                    
    return a.compareTo(b);
                }
            });
            printNames(
    "Names sorted with anonymous inner class:", copy);

            
    // sort array using lambda expression
            copy = Arrays.copyOf(NAMES, NAMES.length);
            Arrays.sort(copy, (a, b) 
    -> a.compareTo(b));
            printNames(
    "Names sorted with lambda expression:", copy);
            
        }
    }
        在清單1中,Lambda被用于取代匿名內部類。這種匿名內部類在應用中非常普遍,所以Lambda表達式很快就贏得了Java8程序員們的青睞。(在本例中,同時使用匿名內部類和Lambda表達式去實現Name類中的一個方法,以方便對這兩種方法進行比較。如果在Lambda中對compareTo()方法進行內聯的話,該表達式將會更加簡潔。)

    標準的函數式接口
        為了應用Lambda,新的包java.util.function中定義了廣泛的函數式接口。它們被歸結為如下幾個類別:
        函數:使用一個參數,基于參數的值返回結果。
        謂語:使用一個參數,基于參數的值返回布爾結果。
        雙函數:使用兩個參數,基于參數的值返回結果。
        供應器:不使用任何參數,但會返回結果。
        消費者:使用一個參數,但不返回任何結果。
    多數類別都包含多個不同的變體,以便能夠作用于基本數據類型的參數和返回值。許多接口所定義的方法都可被用于組合對象,如清單2所示:
    清單2. 組合謂語
    // use predicate composition to remove matching names
    List<Name> list = new ArrayList<>();
    for (Name name : NAMES) {
        list.add(name);
    }
    Predicate
    <Name> pred1 = name -> "Sally".equals(name.firstName);
    Predicate
    <Name> pred2 = name -> "Queue".equals(name.lastName);
    list.removeIf(pred1.or(pred2));
    printNames(
    "Names filtered by predicate:", list.toArray(new Name[list.size()]));
        清單2定義了一對Predicate<Name>變量,一個用于匹配名為Sally的名字,另一個用于匹配姓為Queue的名字。調用方法pred1.or(pred2)會構造一個組合謂語,該謂語先后使用了兩個謂語,當它們中的任何一個返回true時,這個組合謂語就將返回true(這就相當于早期Java中的邏輯操作符||)。List.removeIf()方法就應用這個組合謂語去刪除列表中的匹配名字。
        Java 8定義了許多有用的java.util.function包中接口的組合接口,但這種組合并不都是一樣的。所有的謂語的變體(DoublePredicate,IntPredicate,LongPredicate和Predicate<T>)都定義了相同的組合與修改方法:and(),negate()和or()。但是Function<T>的基本數據類型變體就沒有定義任何組合與修改方法。如果你擁有使用函數式編程語言的經驗,那么你可能就發會發現這些不同之處和奇怪的忽略。

    改變接口
        在Java 8中,接口(如清單1的Comparator)的結構已發生了改變,部分原因是為了讓Lambda更好用。Java 8之前的接口只能定義常量,以及必須被實現的抽象方法。而Java 8中的接口則能夠定義靜態與默認方法。接口中的靜態方法與抽象類中的靜態方法是完全一樣的。默認方法則更像舊式的接口方法,但提供了該方法的一個實現。該方法實現可用于該接口的實現類,除非它被實現類覆蓋掉了。
        默認方法的一個重要特性就是它可以被加入到已有接口中,但又不會破壞已使用了這些接口的代碼的兼容性(除非已有代碼恰巧使用了相同名字的方法,并且其目的與默認方法不同)。這是一個非常強大的功能,Java 8的設計者們利用這一特性為許多已有Java類庫加入了對Lambda表達式的支持。清單3就展示了這樣的一個例子,它是清單1中對名字進行排序的第三種實現方式。
    清單3. 鍵-提取比較器鏈
    // sort array using key-extractor lambdas
    copy = Arrays.copyOf(NAMES, NAMES.length);
    Comparator
    <Name> comp = Comparator.comparing(name -> name.lastName);
    comp 
    = comp.thenComparing(name -> name.firstName);
    Arrays.sort(copy, comp);
    printNames(
    "Names sorted with key extractor comparator:", copy);
        清單3首先展示了如何使用新的Comparator.comparing()靜態方法去創建一個基于鍵-提取(Key-Extraction) Lambda的比較器(從技術上看,鍵-提取Lambda就是java.util.function.Function<T,R>接口的一個實例,它返回的比較器的類型適用于類型T,而提取的鍵的類型R則要實現Comparable接口)。它還展示了如何使用新的Comparator.thenComparing()默認方法去組合使用比較器,清單3就返回了一個新的比較器,它會先按姓排序,再按名排序。
        你也許期望能夠對比較器進行內聯,如:
    Comparator<Name> comp = Comparator.comparing(name -> name.lastName)
        .thenComparing(name 
    -> name.firstName);
    但不幸地是,Java 8的類型推導不允許這么做。為從靜態方法中得到期望類型的結果,你需要為編譯器提供更多的信息,可以使用如下任何一種形式:
    Comparator<Name> com1 = Comparator.comparing((Name name1) -> name1.lastName)
        .thenComparing(name2 
    -> name2.firstName);
    Comparator
    <Name> com2 = Comparator.<Name,String>comparing(name1 -> name1.lastName)
        .thenComparing(name2 
    -> name2.firstName);
        第一種方式在Lambda表達式中加入參數的類型:(Name name1) -> name1.lastName。有了這個輔助信息,編譯才能知道下面它該做些什么。第二種方式是告訴編譯器要傳遞給Function接口(在此處,該接口通過Lambda表達式實現)中comparing()方法的泛型變量T和R的類型。
        能夠方便地構建比較器以及比較器鏈是Java 8中很有用的特性,但它的代價是增加了復雜度。Java 7的Comparator接口定義了兩個方法(compare()方法,以及遍布于每個對象中的equals()方法)。而在Java 8中,該接口則定義了18個方法(除了原有的2個方法,還新加入了9個靜態方法和7個默認方法)。你將發現,為了能夠使用Lambda而造成的這種接口膨脹會重現于相當一部分Java標準類庫中。

    像Lambda那樣使用已有方法
        如果一個存在的方法已經實現了你的需求,你可以直接使用一個方法引用對它進行傳遞。清單4展示了這種方法。
    清單4. 對已有方法使用Lambda

    // sort array using existing methods as lambdas
    copy = Arrays.copyOf(NAMES, NAMES.length);
    comp 
    = Comparator.comparing(Name::getLastName).thenComparing(Name::getFirstName);
    Arrays.sort(copy, comp);
    printNames(
    "Names sorted with existing methods as lambdas:", copy);
        清單4做著與清單3相同的事情,但它使用了已有方法。使用Java 8的形為"類名:方法名"的方法引用語法,你可以使用任意方法,就像Lambda表達式那樣。其效果就與你定義一個Lambda表達式去調用該方法一樣。對類的靜態方法,特定對象或Lambda輸入類型的實例方法(如在清單4中,getFirstName()和getLastName()方法就是Name類的實例方法),以及類構造器,都可以使用方法引用。
        方法引用不僅方便,因為它們比使用Lambda表達式可能更高效,而且為編譯器提供了更好的類型信息(這也就是為什么在上一節的Lambda中使用.thenComparing()構造Comparator會出現問題,而在清單4卻能正常工作)。如果既可以使用對已有方法的方法引用,也可以使用Lambda表達式,請使用前者。

    捕獲與非捕獲Lambda
        你在本文中已見過的Lambda表達式都是非捕獲的,意即,它們都是把傳入的值當作接口方法參數使用的簡單Lambda表達式。Java 8的捕獲Lambda表達式則是使用外圍環境中的值。捕獲Lambda類似于某些JVM語言(如Scala)使用的閉包,但Java 8的實現與之有所不同,因為來自在外圍環境中的值必須聲明為final。也就是說,這些值要么確實為final(就如同以前的Java版本中由匿名內部類所引用的值),要么在外圍環境中不會被修改。這一規范適用于Lambda表達式和匿名內部類。有一些方法可以繞過對值的final限制。例如,在Lambda中僅使用特定變量的當前值,你可以添加一個新的方法,把這些值作為方法參數,再將捕獲的值(以恰當的接口引用這種形式)返回給Lambda。如果期望一個Lambda去修改外圍環境中的值,那么可以用一個可修改的持有器類(Holder)對這些值進行包裝。
        相比于捕獲Lambda,可以更高效地處理非捕獲Lambda,那是因為編譯能夠把它生成為類中的靜態方法,而運行時環境可以直接內聯的調用這些方法。捕獲Lambda也許低效一些,但在相同上下文環境中它至少可以表現的和匿名內部類一樣好。

    幕后的Lambda
        Lambda表達式看起來像匿名內部類,但它們的實現方法不同。Java的內部類有很多構造器;每個內部類都會有一個字節碼級別的獨立類文件。這就會產生大量的重復代碼(大部分是在常量池實體中),類加載時會造成大量的運行時開銷,哪怕只有少量的代碼也會有如此后果。
        Java 8沒有為Lambda生成獨立的類文件,而是使用了在Java 7中引入的invokedynamic字節碼指令。invokedynamic作用于一個啟動方法,當該方法第一次被調用時它會轉而去創建Lambda表達式的實現。然后,該實現會被返回并被直接調用。這樣就避免了獨立類文件帶來的空間開銷,以及加載類的大量運行時開銷。確切地說,Lambda功能的實現被丟給了啟動程序。目前Java 8生成的啟動程序會在運行時為Lambda創建一個新類,但在將來會使用不同的方法去實現。
        Java 8使用的優化使得通過invokedynamic指令實現的Lambda在實際中運行正常。多數其它的JVM語言,包括Scala (2.10.x),都會為閉包使用編譯器生成的內部類。在將來,這些語言可能會轉而使用invokedynamic指令,以便利用到Java 8(及其后繼版本)的優化。

    Lambda的局限
        如在本文開始時我所提到的,Lambda表達式總是某些特殊函數式接口的實現。你可以僅把Lambda當作接口引用去傳遞,而對于其它的接口實現,你也可以只是把Lambda當作這些特定接口去使用。清單5展示了這種局限性,在該示例使用了一對相同的(名稱除外)函數式接口。Java 8編譯接受String::lenght來作為這兩個接口的Lambda實現。但是,在一個Lambd表達式被定義為第一個接口的實例之后,它不能夠用于第二個接口的實例。
    清單5. Lambda的局限
    private interface A {
        
    public int valueA(String s);
    }
    private interface B {
        
    public int valueB(String s);
    }
    public static void main(String[] args) {
        A a 
    = String::length;
        B b 
    = String::length;

        
    // compiler error!
        
    // b = a;

        
    // ClassCastException at runtime!
        
    // b = (B)a;

        
    // works, using a method reference
        b = a::valueA;
        System.out.println(b.valueB(
    "abc"));
    }
        任何對Java接口概念有所了解的人都不會對清單5中的程序感到驚訝,因為那就是Java接口一直所做的事情(除了最后一點,那是Java 8新引入的方法引用)。但是使用其它函數式編程語言,例如Scala,的開發者們則會認為接口的這種限制是不自然的。
        函數式編程語言是用函數類型,而不是接口,去定義變量。在這些編程語言中會很普遍的使用高級函數:把函數作為參數傳遞給其它的函數,或者把函數當作值去返回。其結果就是你會得到比Lambda更為靈活的編程風格,這包括使用函數去組合其它函數以構建語句塊的能力。因為Java 8沒有定義函數類型,你不能使用這種方法去組合Lambda表達式。你可以組合接口(如清單3所示),但只能是與Java 8中已寫好的那些接口相關的特定接口。僅在新的java.util.function包內,就特殊設定了43個接口去使用Lambda。把它們加入到數以百計的已有接口中,你將看到這種方法在組合接口時總是會有嚴重的限制。
        使用接口而不是在向Java中引入函數類型是一個精妙的選擇。這樣就在防止對Java類庫進行重大改動的同時也能夠對已有類庫使用Lambda表達式。它的壞作用就是對Java 8造成了極大的限制,它只能稱為"接口編程"或是類函數式編程,而不是真正的函數式編程。但依靠JVM上其它語言,也包括函數式語言,的優點,這些限制并不可怕。

    結論
        Lambda是Java語言的最主要擴展,伴著它們的兄弟新特性--方法引用,隨著程序被移植到Java 8,Lambda將很快成為所有Java開發者不可或缺的工具。當與Java 8流結合起來時,Lambda就特別有用。查看文章"JVM并發: Java 8并發基礎",可以了解到將Lambda和流結合起來使用是如何簡化并發編程以及提高程序效率的。
    posted on 2014-04-19 23:48 John Jiang 閱讀(3156) 評論(0)  編輯  收藏 所屬分類: JavaSEJava翻譯
    主站蜘蛛池模板: 国产成人免费在线| 久久亚洲精品成人AV| 免费黄色网址网站| 又硬又粗又长又爽免费看 | 亚洲熟妇av一区二区三区漫画 | 久久久久亚洲AV成人片| 免费人成网站在线播放| 青青草a免费线观a| 日韩免费人妻AV无码专区蜜桃| japanese色国产在线看免费| 亚洲爆乳少妇无码激情| 亚洲综合色7777情网站777| 亚洲成a人片在线观看中文动漫| 亚洲色无码一区二区三区| 免费在线观看黄色毛片| 青青草国产免费久久久91| 在线观看的免费网站| 1000部拍拍拍18勿入免费视频软件| 久久精品免费一区二区三区| gogo免费在线观看| 国产VA免费精品高清在线| 美女无遮挡免费视频网站| 欧美激情综合亚洲一二区| 亚洲欧美国产国产综合一区| 亚洲AV无码成人专区| 亚洲乱码一二三四区麻豆| 亚洲成人一级电影| 亚洲永久中文字幕在线| 久久亚洲精品无码VA大香大香| 久久久亚洲精品国产| 亚洲电影一区二区三区| 亚洲成人午夜在线| 亚洲国产精品不卡在线电影| 亚洲视频在线视频| 亚洲网红精品大秀在线观看| 亚洲自偷精品视频自拍| 亚洲午夜电影在线观看| 亚洲中文无码永久免费| 色偷偷噜噜噜亚洲男人| 青青草97国产精品免费观看 | 亚洲综合伊人久久综合|