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表達式只是對抽象方法的實現,並沒有創建接口的實現類對象,因爲我們只是想使用這個抽象方法的實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章