Java8的Lambda表示式

王知無發表於2019-03-30

è·ä¸Java8 - äºè§£lambda

從Java8出現以來lambda是最重要的特性之一,它可以讓我們用簡潔流暢的程式碼完成一個功能。


很長一段時間java被吐槽是冗餘和缺乏函數語言程式設計能力的語言,隨著函數語言程式設計的流行java8種也引入了這種程式設計風格。在此之前我們都在寫匿名內部類幹這些事,但有時候這不是好的做法,本文中將介紹和使用lambda,帶你體驗函數語言程式設計的魔力。

什麼是lambda?

lambda表示式是一段可以傳遞的程式碼,它的核心思想是將物件導向中的傳遞資料變成傳遞行為。
我們回顧一下在使用java8之前要做的事,之前我們編寫一個執行緒時是這樣的:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("do something.");      
    }
}
複製程式碼

也有人會寫一個類去實現Runnable介面,這樣做沒有問題,我們注意這個介面中只有一個run方法,
當把Runnable物件給Thread物件作為構造引數時建立一個執行緒,執行後將輸出do something.。
我們使用匿名內部類的方式實現了該方法。

這實際上是一個程式碼即資料的例子,在run方法中是執行緒要執行的一個任務,但上面的程式碼中任務內容已經被規定死了。
當我們有多個不同的任務時,需要重複編寫如上程式碼。

設計匿名內部類的目的,就是為了方便 Java 程式設計師將程式碼作為資料傳遞。不過,匿名內部 類還是不夠簡便。
為了執行一個簡單的任務邏輯,不得不加上 6 行冗繁的樣板程式碼。那如果是lambda該怎麼做?

Runnable r = () -> System.out.println("do something.");
複製程式碼

嗯,這程式碼看起來很酷,你可以看到我們用 () -> 的方式完成了這件事,這是一個沒有名字的函式,也沒有人和引數,再簡單不過了。
使用 -> 將引數和實現邏輯分離,當執行這個執行緒的時候執行的是 -> 之後的程式碼片段,且編譯器幫助我們做了型別推導;
這個程式碼片段可以是用 {} 包含的一段邏輯。下面一起來學習一下lambda的語法。

基礎語法

lambda中我們遵循如下的表示式來編寫:

expression = (variable) -> action
複製程式碼
  • variable: 這是一個變數,一個佔位符。像x,y,z,可以是多個變數。
  • action: 這裡我稱它為action, 這是我們實現的程式碼邏輯部分,它可以是一行程式碼也可以是一個程式碼片段

可以看到Java中lambda表示式的格式:引數、箭頭、以及動作實現,當一個動作實現無法用一行程式碼完成,可以編寫
一段程式碼用 {} 包裹起來。

lambda表示式可以包含多個引數,例如:

BinaryOperator<Integer> sumFunction = (x, y) -> x + y;
int sum = sumFunction.apply(1, 2);
複製程式碼

這時候我們應該思考這段程式碼不是之前的x和y數字相加,而是建立了一個函式,用來計算兩個運算元的和。
後面用int型別進行接收,在lambda中為我們省略去了return

函式式介面

函式式介面是隻有一個方法的介面,用作lambda表示式的型別。前面寫的例子就是一個函式式介面,來看看jdk中的Runnable原始碼

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
複製程式碼

這裡只有一個抽象方法run,實際上你不寫public abstract也是可以的,在介面中定義的方法都是public abstract的。
同時也使用註解@FunctionalInterface告訴編譯器這是一個函式式介面,當然你不這麼寫也可以,標識後明確了這個函式中
只有一個抽象方法,當你嘗試在介面中編寫多個方法的時候編譯器將不允許這麼幹。

嘗試函式式介面

我們來編寫一個函式式介面,輸入一個年齡,判斷這個人是否是成人。

public class FunctionInterfaceDemo {
    @FunctionalInterface
    interface Predicate<T> {
        boolean test(T t);
    }
    /**
     * 執行Predicate判斷
     *
     * @param age       年齡
     * @param predicate Predicate函式式介面
     * @return          返回布林型別結果
     */
    public static boolean doPredicate(int age, Predicate<Integer> predicate) {
        return predicate.test(age);
    }

    public static void main(String[] args) {
        boolean isAdult = doPredicate(20, x -> x >= 18);
        System.out.println(isAdult);
    }
}
複製程式碼

從這個例子我們很輕鬆的完成 是否是成人 的動作,其次判斷是否是成人,在此之前我們的做法一般是編寫一個
判斷是否是成人的方法,是無法將 判斷 共用的。而在本例只,你要做的是將 行為 (判斷是否是成人,或者是判斷是否大於30歲)
傳遞進去,函式式介面告訴你結果是什麼。

實際上諸如上述例子中的介面,偉大的jdk設計者為我們準備了java.util.function

Java8的Lambda表示式

我們前面寫的Predicate函式式介面也是JDK種的一個實現,他們大致分為以下幾類:

Java8的Lambda表示式

消費型介面示例

public static void donation(Integer money, Consumer<Integer> consumer){
    consumer.accept(money);  
}
public static void main(String[] args) {
    donation(1000, money -> System.out.println("好心的麥樂迪為Blade捐贈了"+money+"元")) ;
}
複製程式碼

供給型介面示例

public static List<Integer> supply(Integer num, Supplier<Integer> supplier){
       List<Integer> resultList = new ArrayList<Integer>()   ;
       for(int x=0;x<num;x++)  
           resultList.add(supplier.get());
       return resultList ;
}
public static void main(String[] args) {
    List<Integer> list = supply(10,() -> (int)(Math.random()*100));
    list.forEach(System.out::println);
}
複製程式碼

函式型介面示例

轉換字串為Integer

public static Integer convert(String str, Function<String, Integer> function) {
    return function.apply(str);
}
public static void main(String[] args) {
    Integer value = convert("28", x -> Integer.parseInt(x));
}
複製程式碼

斷言型介面示例

篩選出只有2個字的水果

public static List<String> filter(List<String> fruit, Predicate<String> predicate){
    List<String> f = new ArrayList<>();
    for (String s : fruit) {
        if(predicate.test(s)){
            f.add(s);
        }
    }
    return f;
}
public static void main(String[] args) {
    List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴蓮", "火龍果", "水蜜桃");
    List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
    System.out.println(newFruit);
}
複製程式碼

預設方法

在Java語言中,一個介面中定義的方法必須由實現類提供實現。但是當介面中加入新的API時,
實現類按照約定也要修改實現,而Java8的API對現有介面也新增了很多方法,比如List介面中新增了sort方法。
如果按照之前的做法,那麼所有的實現類都要實現sort方法,JDK的編寫者們一定非常抓狂。

幸運的是我們使用了Java8,這一問題將得到很好的解決,在Java8種引入新的機制,支援在介面中宣告方法同時提供實現
這令人激動不已,你有兩種方式完成 1.在介面內宣告靜態方法 2.指定一個預設方法。

我們來看看在JDK8中上述List介面新增方法的問題是如何解決的

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
複製程式碼

翻閱List介面的原始碼,其中加入一個預設方法default void sort(Comparator<? super E> c)
在返回值之前加入default關鍵字,有了這個方法我們可以直接呼叫sort方法進行排序。

List<Integer> list = Arrays.asList(2, 7, 3, 1, 8, 6, 4);
list.sort(Comparator.naturalOrder());
System.out.println(list);
複製程式碼

Comparator.naturalOrder()是一個自然排序的實現,這裡可以自定義排序方案。
你經常看到使用Java8操作集合的時候可以直接foreach的原因也是在Iterable介面中也新增了一個預設方法:forEach
該方法功能和 for 迴圈類似,但是允許 使用者使用一個Lambda表示式作為迴圈體。

福利部分:

《大資料成神之路》

《幾百TJava和大資料資源下載》


Java8的Lambda表示式


相關文章