1. 程式人生 > >Java8之熟透Lambda表示式

Java8之熟透Lambda表示式

一、Lambda簡述

1.1、Lambda概述

​ Lambda 表示式可以理解為簡潔地表示可傳遞的匿名函式的一種方式:它沒有名稱,但它有引數列表、函式主體、返回型別,可能還有一個可以丟擲的異常列表。

  • 匿名:它不像普通方法那樣有一個明確的名稱;
  • 函式:Lambda 表示式是函式是因為它不像方法那樣屬於某個特定的類,但和方法一樣,Lambda 有引數列表、函式主體、返回型別,還可能有可以丟擲的異常列表;
  • 傳遞:Lambda 表示式可以作為引數傳遞給方法或儲存在變數中;
  • 簡潔:無需像匿名類那樣寫很多模板程式碼;

So That:

  • lambada 表示式實質上是一個匿名方法,但該方法並非獨立執行,而是用於實現由函式式介面定義的唯一抽象方法
  • 使用 lambda 表示式時,會建立實現了函式式介面的一個匿名類例項
  • 可以將 lambda 表示式視為一個物件,可以將其作為引數傳遞

1.2、Lambda簡介

Lambda 表示式是一個匿名函式(對於 Java 而言並不很準確,但這裡我們不糾結這個問題)。簡單來說,這是一種沒有宣告的方法,即沒有訪問修飾符,返回值宣告和名稱。

Java 中的 Lambda 表示式通常使用語法是 (argument) -> (body):

(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }

Lambda 表示式舉例:

(int a, int b) -> {  return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };

1.3、Lambda表示式組成與結構

1.3.1、Lambda表示式組成

Lambda 表示式由引數列表、箭頭和 Lambda 主體組成。

(Apple o1, Apple o2) -> Integer.valueOf(o1.getWeight()).compareTo(Integer.valueOf(o2.getWeight()))
  1. 引數列表:這裡採用了 Comparator 中 compareTo 方法的引數;
  2. 箭頭:箭頭把引數列表和 Lambda 主體分開;
  3. Lambda 主體:表示式就是 Lambda 的返回值;
1.3.2、Lambda表示式結構

1)Lambda 表示式的結構

  • Lambda 表示式可以具有零個,一個或多個引數。
  • 可以顯式宣告引數的型別,也可以由編譯器自動從上下文推斷引數的型別。例如 (int a) 與剛才相同 (a)
  • 引數用小括號括起來,用逗號分隔。例如 (a, b)(int a, int b)(String a, int b, float c)
  • 空括號用於表示一組空的引數。例如 () -> 42
  • 當有且僅有一個引數時,如果不顯式指明型別,則不必使用小括號。例如 a -> return a*a
  • Lambda 表示式的正文可以包含零條,一條或多條語句。
  • 如果 Lambda 表示式的正文只有一條語句,則大括號可不用寫,且表示式的返回值型別要與匿名函式的返回型別相同。
  • 如果 Lambda 表示式的正文有一條以上的語句必須包含在大括號(程式碼塊)中,且表示式的返回值型別要與匿名函式的返回型別相同。

2)有效Lambda 表示式舉例

Lambda 表示式 含義
(String s) -> s.length() 表示式具有一個 String 型別的引數並返回一個 int。 Lambda 沒有 return 語句,因為已經隱含的 return,可以顯示呼叫 return。
(Apple a) -> a.getWeight() > 150 表示式有一個 Apple 型別的引數並返回一個 boolean 值
(int x, int y) -> { System.out.printn("Result"); System.out.printn(x + y)} 表示式具有兩個 int 型別的引數而沒有返回值(void返回),Lambda 表示式可以包含多行語句,但必須要使用大括號包起來。
() -> 42 表示式沒有引數,返回一個 int 型別的值。
(Apple o1, Apple o2) -> Integer.valueOf(o1.getWeight()) .compareTo (Integer.valueOf(o2.getWeight())) 表示式具有兩個 Apple 型別的引數,返回一個 int 比較重要。

3)Lambda 表示式的使用舉例

使用案例 Lambda 示例
布林表示式 (List<String> list) -> list.isEmpty()
建立物件 () -> new Apple(10)
消費物件 (Apple a) -> { System.out.println(a.getWeight) }
從一個物件中選擇/抽取 (String s) -> s.lenght()
組合兩個值 (int a, int b) -> a * b
比較兩個物件 `(Apple o1, Apple o2) ->
Integer.valueOf(o1.getWeight())
.compareTo(Integer.valueOf(o2.getWeight()))

二、使用Lambda表示式

2.1、函式式介面

函式式介面就是隻定義一個抽象方法的介面,比如 Java API 中的 Predicate、Comparator 和 Runnable 等。

public interface Predicate<T> {
    boolean test(T t);
}
public interface Comparator<T> {
    int compare(T o1, T o2);
}
public interface Runnable {
    void run();
}

函式式介面作用是什麼?

Lambda 表示式允許你直接以內聯的形式為函式式介面的抽象方法提供實現,並把整個表示式作為函式式介面的例項(具體說來,是函式式介面一個具體實現 的例項)。你用匿名內部類也可以完成同樣的事情,只不過比較笨拙:需要提供一個實現,然後 再直接內聯將它例項化。

下面的程式碼是有效的,因為Runnable是一個只定義了一個抽象方法run 的函式式介面:

//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");

//匿名類
Runnable r2 = new Runnable(){ 
    public void run(){ 
        System.out.println("Hello World 2"); 
    } 
};

public static void process(Runnable r){ 
    r.run(); 
} 

process(r1); //列印 "Hello World 1"
process(r2); //列印 "Hello World 2"
//利用直接傳遞的 Lambda 列印 "Hello World 3"
process(() -> System.out.println("Hello World 3"));

2.2、通過示例感受Lambda

1)之前做法

Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};

2)現在做法

Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

3)再通過一個明顯的例項

public static void rawUseMethod(){
        List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.compareTo(b);
            }
        });

        for (String str : names){
            System.out.println(str);
        }
    }

    public static void useLambda1(){
        List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
        Collections.sort(names,(String a,String b) -> {
            return a.compareTo(b);
        });
        for (String str : names){
            System.out.println(str);
        }
    }

    public static void useLambda2(){
        List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
         Collections.sort(names,(String a,String b) -> a.compareTo(b));
        for (String str : names){
            System.out.println(str);
        }
    }

    public static void useLambda3(){
        List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
        names.sort((String a,String b) -> a.compareTo(b));
        //當然也可以直接去掉引數型別,直接推匯出來即可
        names.sort((a,b) -> a.compareTo(b));
        for (String str : names){
            System.out.println(str);
        }
    }

2.3、Lambda語法規則


Lambda表示式有三個部分:
1) 引數列表
這裡它採用了Comparator中compare方法的引數,兩個Apple。
2)箭頭
箭頭->把引數列表與Lambda主體分隔開。
3)Lambda主體
比較兩個Apple的重量。表示式就是Lambda的返回值了。

為了進一步說明,下面給出了Java 8中五個有效的Lambda表示式的例子。

| 布林表示式 | (List list) -> list.isEmpty() |
| --------------------- | ------------------------------------------------------------ |
| 建立物件 | () -> new Apple(10) |
| 消費一個物件 | (Apple a) -> { System.out.println(a.getWeight()); } |
| 從一個物件中選擇/抽取 | (String s) -> s.length() |
| 組合兩個值 | (int a, int b) -> a * b |
| 比較兩個物件 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |

三、Functional介面(函式式介面)

3.1、概述

"函式式介面"是指僅僅只包含一個抽象方法的介面,每一個函式式介面型別的lambda表示式都自動被匹配到這個抽象方法。因為 預設方法 不算抽象方法,所以你也可以給你的函式式介面新增預設方法。

我們可以將lambda表示式當作任意只包含一個抽象方法的介面型別,為了確保你的介面確實是達到這個要求的,可以介面上新增 @FunctionalInterface 註解,編譯器如果發現你標註了這個註解的介面有多於一個抽象方法的時候會報錯的。

3.2、舉例說明

1)定義函式式介面

    //這個註解不加也可以,加上只是為了讓編譯器檢查
    @FunctionalInterface
    interface Action{
        public void run();

        default void doSomething(){
            System.out.println("doSomething..");
        }
    }
    //這個註解不加也可以,加上只是為了讓編譯器檢查
    @FunctionalInterface
    interface Work<T,V>{
        public V doWork(T t);
    }

2)使用

    public class LambdaTest2 {
    
        public static void main(String[] args) {
            
            //原來的內部類實現方式
            test(new Action(){
                @Override
                public void run() {
                    System.out.println("run..");
                }
            });
            
            //lambda表示式方法
            test(()->System.out.println("run"));

            
            //也可以先建立物件
            Action a = ()->System.out.println("run...");
            System.out.println(a.getClass());
            test(a);

            //介面中有泛型也可以,只關注方法的引數和返回值
            Work<String,Integer> w = (v)->v.length();
            run(w);

            run((v)->v.length());

            //如果引數只有一個,那麼還可以這樣簡寫: 去掉小括號
            //注意程式碼就一句,作為返回值的話不用寫return
            run(v->v.length());
            
            //有多句程式碼,就需要寫{}了,並且需要寫return
            run(v->{
                System.out.println("doWork..");
                return v.length();
            });

            //觀察下面程式碼是什麼意思
            run(v->1);
            
        }

        public static void test(Action a){
            a.run();
            a.doSomething();
        }
        
        public static void run(Work<String,Integer> a){
            int i = a.doWork("hello");
            System.out.println(i);
        }
        
    }

注意:

lambda表示式無法訪問介面的預設方法,lambda表示式只能去匹配對應介面中的唯一抽象方法。

相當於lambda表示式只是對抽象方法的實現,並沒有建立介面的實現類物件,因為我們只是想使用這個抽象方法的實現