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

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

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

    LetsCoding.cn

    天地之間有桿秤,拿秤砣砸老百姓。

    Java 8:Lambda表達式(二)

    Java 8中,最重要的一個改變讓代碼更快、更簡潔,并向FP(函數式編程)打開了方便之門。下面我們來看看,它是如何做到的。

    上一篇中,你看到了Java中Lambda表達式的一種形式:參數 + “->” + 表達式。如果代碼實現的邏輯一條語句完成不了,你可以寫成類似方法的形式:代碼寫在“{}”中,再加上顯式的return語句。例如:

    1. (String first, String second) -> {
    2.      if (first.length() < second.length()) return -1;
    3.      else if (first.length() > second.length()) return 1;
    4.      else return 0;
    5. }

    就算一個Lambda表達式沒有參數,你也需要保留空的小括號,就像沒有參數的方法一樣:

    1. () -> { for (int i = 0; i < 1000; i ++) doWork(); }

    如果一個Lambda表達式的參數類型,可以根據上下文推斷出來,你可以省略它們。例如:

    1. Comparator< String > comp
    2.      = (first, second) // Same as (String first, String second)
    3.         -> Integer.compare(first.length(), second.length());

    這里,編譯器能夠推斷出first和second肯定是字符串類型,因為,這個Lambda表達式被賦值給了字符串Comparator。

    如果Lambda表達式只有一個單獨的、可以推斷出的參數,你甚至可以省略兩邊的小括號:

    1. EventHandler< ActionEvent > listener = event ->
    2.      System.out.println("Thanks for clicking!");
    3.      // Instead of (event) -> or (ActionEvent event) ->

    就像你可以給方法的參數加上注解或final修飾符一樣,Lambda表達式也可以:

    1. (final String name) -> ...
    2. (@NonNull String name) -> ...

    你永遠不能指定Lambda表達式的返回值類型,它只能從上下文去推斷出來。例如,表達式

    1. (String first, String second) -> Integer.compare(first.length(), second.length())

    可以用在需要int類型的上下文中。

    注意,只在部分分支中有返回值,而在其他分支中沒有返回值的Lambda表達式是非法的。例如,

    1. (int x) -> { if (>0) return 1; }
    2. // invalid Lambda expression

    函數式接口

    正如我們討論過的,Java中存在很多只包含代碼塊的接口,例如Runnable或Comparator。Lambda表達式向后兼容這些接口。

    任何在需要只包含一個抽象方法的接口的實例的時候,你都可以用Lambda表達式。這些接口被稱為“函數式接口”。

    你可能會想,為什么一個函數式接口必須只包含一個抽象方法呢?接口中所有的方法不都是抽象的嗎?實際上,接口一直都是可以重新聲明Object類中包含的方法的,比如toString或者clone,而這樣的重新聲明并不會使這些方法變成抽象的。(有些接口,為了在生成的javadoc中添加自己的注釋,而重新聲明了Object中的方法,例如可以去翻翻Comparator接口的API。)更重要的是,你馬上就會看到,在Java 8中,接口可以聲明非抽象的方法。

    為了展示到成函數式接口的轉換,看看Arrays.sort方法。它的第二個參數需要一個只包含一個方法的Comparator接口的實例。簡單的給它提供一個Lambda表達式:

    1. Arrays.sort(words,
    2.      (first, second) -> Integer.compare(first.length(), second.length()));

    在幕后,Arrays.sort方法會接收到一個實現了Comparator接口的某個類的實例,調用它的compare方法就會執行Lambda表達式。管理這些實例和類是完全依賴于實現的,它比使用傳統的內部類更加有效率。最好是把Lambda表達式當成函數來看,而不是對象,并認可,它可以被賦值給一個函數式接口。

    這種到接口的轉換,令Lambda表達式如此的引人注目,語法很短,很簡單。下面是另外一個例子:

    1. button.setOnAction(event ->
    2.      System.out.println("Thanks for clicking!"));

    這讀起來太簡單了!

    實際上,轉型成函數式接口,是你在Java中唯一可以對Lambda表達式做的事情。在其他支持函數字面量的語言里,你可以聲明函數類型,比如(String, String) -> int,聲明這種函數類型的變量,使用這些變量保存函數表達式。在Java中,你甚至不能把Lambda表達式賦值給一個Object類型的變量,因為Object不是一個函數式接口。Java的設計者們決定嚴格堅持熟悉的接口概念,而不是在語言中添加新的函數類型。

    Java API的java.util.function中定義了幾個范型的函數式接口。其中一個接口,BiFunction,描述了擁有參數T和U,返回值是R的函數。你可以把我們字符串比較的Lambda表達式保存在這種類型的變量中:

    1. BiFunction< StringStringInteger > comp
    2.      = (first, second) -> Integer.compare(first.length(), second.length());

    但是,那樣并不能幫你做排序,因為Arrays.sort方法不接受BiFunction類型的變量作為參數。如果你以前使用過FP語言,你會發現這很奇怪。 但是對Java開發者來說,這很自然。一個接口,例如Comparator,擁有一個特定的目的,而不只是一個給定參數和返回值類型的方法。Java 8保留了這種特色。當你想用Lambda表達式做事情的時候,你依然要牢記表達式的目的,并給它一個特定的函數式接口。

    幾個Java 8的API用到了java.util.function中的函數式接口,將來,你也許能看到,其他地方也會用到它們。但是,請要記住,你可以很好的把Lambda表達式轉型成函數式接口,這是現今你使用的API的一部分。你也可以給任何函數式接口加上@FunctionalInterface注解,這樣做有兩個好處。一是編譯器會去檢查被注解的接口,是不是只有一個抽象方法。另一個是,在生成的javadoc頁面中,會包含類似這樣的一句話:本接口是函數式接口。這個注解不是必須的,因為根據定義,任何只有一個抽象方法的接口都是函數式接口。但使用@FunctionalInterface注解會是個不錯的主意。

    最后,檢查型異常,會影響Lambda表達式轉型成函數式接口實例。如果Lambda表達式語句體中拋出了檢查型異常,這個異常需要在目標接口中的抽象方法里聲明。例如,下面的代碼就有問題:

    1. Runnable sleeper = () -> { System.out.println("Zzz"); Thread.sleep(1000); };
    2. // Error: Thread.sleep can throw a checkedInterruptedException

    這個賦值是非法的,因為Runnable.run方法不能拋出任何異常。要修改它,你有兩個選擇。你可以在Lambda表達式語句體中捕獲這個異常。或者,你可以把這個表達式,賦值給一個抽象方法能拋出異常的接口實例。例如,Callable的call方法可以拋出任何異常,因此,你可以把上面的表達式賦值給Callable(如果你增加一個返回null的return語句)。

    方法引用

    有時候,已經有方法實現了你想要傳遞給其他代碼的邏輯。比如,假定任何時候按鈕被點擊,你只是想要打印事件對象,你肯定會這樣做:

    1. button.setOnAction(event -> System.out.println(event));

    如果能夠只把println方法傳遞給setOnAction方法,那就更好了。下面就是這樣做的:

    1. button.setOnAction(System.out::println);

    表達式System.out::println就是一個方法引用,它等價于x -> System.out.println(x)。

    另外一個例子,假如你想忽略大小寫的給字符串排序。你可以這樣:

    1. Arrays.sort(strings, String::compareToIgnoreCase)

    正如你看到的,“::”操作符把對象名或類名跟方法名分隔開來。主要有三種情況:

    • 對象::實例方法
    • 類::靜態方法
    • 類::實例方法

    前兩種,方法引用等價于提供方法參數的Lambda表達式。正如上文提到的,System.out::println等價于x -> System.out.println(x)。同樣的,Math::pow等價于(x, y) -> Math.pow(x, y)。最后一種情況里,第一個參數為方法的調用目標。比如,String::compareToIgnoreCase跟(x,y) -> x.compareToIgnoreCase(y)等價。

    當出現多個重載的同名方法時,編譯器會根據上下文,嘗試找出你實際想用的那一個。例如,Math.max方法有兩個版本,一個的參數類型是整型,一個是雙精度型。哪一個會被用到,取決于Math::max會轉型成擁有哪種方法參數的函數式接口。和Lambda表達式一樣,方法引用并不是單獨存在的,它們總是被轉型為函數式接口。

    在方法引用中,可以使用this關鍵字。例如,this::equals等價于x -> this.equals(x)。super也一樣。表達式supper::instanceMethod使用this作為目標,調用指定方法的父類版本。下面的代碼故意寫成那樣,來展示工作機制:

    1. class Greeter {
    2.      public void greet() {
    3.         System.out.println("Hello, world!");
    4.      }
    5. }
    6.    
    7. class ConcurrentGreeter extends Greeter {
    8.      public void greet() {
    9.         Thread t = new Thread(super::greet);
    10.         t.start();
    11.      }
  • }
  • 當線程啟動時,它的Runnable被調用,super::greet執行父類Greeter的greet方法。(注意在內部類中,你可以像這樣使用this來指代內部類的實例:EnclosingClosing.this::method或者EnclosingClass.super::method。)

    構造方法引用

    除了把方法名改成new以外,構造方法引用基本和方法引用一樣。例如,Button::new是一個Button的構造方法引用。哪一個構造方法被調用,取決于上下文。想象一下,你有一個字符串列表。那么通過用每一個字符串去調用Button的構造方法,你可把字符串列表轉換成一個按鈕數組。

    1. List< String > labels = ...;
    2. Stream< Button > stream = labels.stream().map(Button::new);
    3. List< Button > buttons = stream.collect(Collectors.toList());

    stream、map和collect方法的細節不在本文范圍之內。現在,重要的是,map方法為每一個字符串,調用構造方法Button(String)。Button類有很多構造方法,但是編譯器會選擇用字符串為參數的那一個,因為它從上下文中推斷出,構造方法會被使用一個字符串參數來調用。

    你可以用數組類型來組成創建方法引用。例如,int[]::new就是構造方法引用,它有一個參數:數組長度。它等價于x -> new int[x]。

    數組的構造方法引用,對克服Java的限制很有用。我們不能創建一個以范型類型T為元素的數組。表達式new T[n]是不對的,因為它在編譯時,被擦除為new Object[n]。對類庫的作者來說,這是一個問題。例如,我們想擁有一個按鈕的數組。Stream接口有一個返回Object數組的方法,toArray:

    1. Object[] buttons = stream.toArray();

    然而,這并不能令人滿意。我們想要的是按鈕數組,而不是Object數組。stream庫用構造方法引用解決了這個問題。把Button[]::new傳遞給toArray方法:

    1. Button[] buttons = stream.toArray(Button[]::new);

    toArray方法調用構造方法得到正確的數組類型,然后填充并返回數組。

    本文譯自:Lambda Expressions in Java 8

    原創文章,轉載請注明: 轉載自LetsCoding.cn
    本文鏈接地址: Java 8:Lambda表達式(二)

    posted on 2014-05-11 12:07 Rolandz 閱讀(1829) 評論(2)  編輯  收藏 所屬分類: 編程實踐

    評論

    # re: Java 8:Lambda表達式(二) 2014-05-11 21:45 服裝搭配技巧

    程序工作者,辛苦了,謝謝分享.
    加油!!也希望樓主有空去我網站玩玩。www.oradre.com  回復  更多評論   

    # re: Java 8:Lambda表達式(二) 2014-05-13 01:02 非凡娛樂

    很有幫助,多謝樓主!  回復  更多評論   

    導航

    統計

    留言簿(1)

    隨筆分類(12)

    隨筆檔案(19)

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 免费观看无遮挡www的视频| 无码不卡亚洲成?人片| 亚洲AV永久无码精品网站在线观看 | 日韩免费电影网站| 久久精品亚洲AV久久久无码| 黄网址在线永久免费观看| jizz免费在线影视观看网站| 噜噜噜亚洲色成人网站∨| 日韩在线看片免费人成视频播放| 国产免费一区二区三区免费视频| 久久精品国产亚洲AV无码麻豆| 免费羞羞视频网站| a级毛片黄免费a级毛片| 亚洲五月综合缴情婷婷| 久久久久亚洲AV成人网| 曰批视频免费30分钟成人| 一级毛片a女人刺激视频免费| 亚洲精品无码久久久久久久| 亚洲成AⅤ人影院在线观看| 中文字幕亚洲免费无线观看日本| 精品国产亚洲第一区二区三区| 亚洲国产精品一区二区成人片国内| 99久久99久久精品免费看蜜桃| 在线观看亚洲专区| 亚洲最大黄色网址| 国产亚洲AV手机在线观看| 最近2019中文字幕免费看最新| 成人国产精品免费视频| 亚洲乱码国产乱码精华| 噜噜噜亚洲色成人网站∨| 激情97综合亚洲色婷婷五 | 国产日产成人免费视频在线观看| 免费福利电影在线观看| 污网站在线观看免费| 亚洲午夜在线播放| 亚洲国产精品自在线一区二区| 亚洲一级片免费看| 国产成人免费高清在线观看 | 国产免费一区二区三区VR| 亚洲一级毛片免费看| 在线成人精品国产区免费|