JavaRush /Java Blog /Random-TW /Lambda 表達式及範例

Lambda 表達式及範例

在 Random-TW 群組發布
Java最初是一種完全物件導向的語言。除了原始型別之外,Java 中的一切都是物件。甚至連陣列也是物件。每個類別的實例都是物件。不存在單獨定義函數的可能性(在類別之外 -近似翻譯)。並且無法將方法作為參數傳遞或傳回方法體作為另一個方法的結果。就像那樣。但這是在 Java 8 之前的事。 Lambda 表達式及範例 - 1從古老的 Swing 時代開始,當需要將某些功能傳遞給某些方法時,就必須編寫匿名類別。例如,新增事件處理程序如下所示:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
這裡我們要在滑鼠事件監聽器上加入一些程式碼。我們定義了一個匿名類別MouseAdapter並立即從中建立了一個物件。透過這種方式,我們已經將附加功能傳遞到了addMouseListener. 簡而言之,在Java中透過參數傳遞一個簡單的方法(功能)並不容易。這項限制迫使 Java 8 開發人員在語言規格中加入 Lambda 表達式等功能。

為什麼Java需要Lambda表達式?

從一開始,Java 語言就沒有太大的發展,除了註解、泛型等。首先,Java 總是保持物件導向。使用過 JavaScript 等函數式語言後,我們就能理解 Java 是如何嚴格物件導向和強型別的。Java 中不需要函數。在 Java 世界中無法單獨找到它們。在函數式程式語言中,函數脫穎而出。它們獨立存在。您可以將它們指派給變數並將它們通過參數傳遞給其他函數。JavaScript 是函數式程式語言的最佳範例之一。您可以在 Internet 上找到詳細介紹 JavaScript 作為函數式語言的優點的好文章。函數式語言擁有 Closure 等強大的工具,與傳統的應用程式編寫方法相比,它具有許多優勢。閉包是一個附有環境的函數 - 一個儲存對該函數的所有非局部變數的參考的表。在Java中,可以透過Lambda表達式來模擬閉包。當然,閉包和 Lambda 表達式之間存在差異,而且差異不小,但 Lambda 表達式是閉包的一個很好的替代方案。在他諷刺而有趣的部落格中,Steve Yegge 描述了 Java 世界如何與名詞(實體、物件 -大約翻譯)嚴格聯繫在一起。如果您還沒有讀過他的博客,我推薦您閱讀。他用有趣的方式描述了 Lambda 表達式被添加到 Java 中的確切原因。Lambda 表達式為 Java 帶來了長期以來缺少的功能。Lambda 表達式就像物件一樣為語言帶來功能。雖然這不是 100% 正確,但您可以看到 Lambda 表達式雖然不是閉包,但提供了類似的功能。在函數式語言中,lambda 表達式是函數;但在 Java 中,lambda 表達式由物件表示,並且必須與稱為函數式介面的特定物件類型關聯。接下來我們就來看看它是什麼。Mario Fusco 的文章《Why we need Lambda Expression in Java》詳細描述了為什麼所有現代語言都需要閉包功能。

Lambda 表達式簡介

Lambda 表達式是匿名函數(對 Java 來說可能不是 100% 正確的定義,但它帶來了一些清晰度)。簡單來說,這是一個沒有聲明的方法,即 沒有存取修飾符,傳回值和名稱。簡而言之,它們允許您編寫一個方法並立即使用它。它在一次性方法呼叫的情況下特別有用,因為 減少了聲明和編寫方法所需的時間,而無需建立類別。Java 中的 Lambda 表達式通常具有以下語法(аргументы) -> (тело)。例如:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
以下是真實 Lambda 表達式的一些範例:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Lambda 表達式的結構

讓我們來研究一下lambda表達式的結構:
  • Lambda 表達式可以有 0 個或多個輸入參數。
  • 參數的類型可以明確指定,也可以從上下文中取得。例如( int a)可以寫成這樣( a)
  • 參數括在括號內並用逗號分隔。例如 ( a, b) 或 ( int a, int b) 或 ( String a, int b, float c)
  • 如果沒有參數,則需要使用空括號。例如() -> 42
  • 當只有一個參數時,如果沒有明確指定類型,則括號可以省略。例子:a -> return a*a
  • Lambda 表達式的主體可以包含 0 個或多個表達式。
  • 如果主體由單一語句組成,則可以不將其括在花括號中,並且可以在不使用關鍵字 的情況下指定傳回值return
  • 否則,需要大括號(程式碼區塊),並且必須在末尾使用關鍵字指定傳回值return(否則傳回類型將為void)。

什麼是函數式介面

在Java中,Marker介面是沒有宣告方法或欄位的介面。換句話說,令牌介面是空接口。類似地,函數式介面是僅宣告一個抽象方法的介面。 java.lang.Runnable是一個函數式介面的例子。它只聲明一種方法void run()。還有一個介面ActionListener- 也很實用。以前,我們必須使用匿名類別來建立實作函數介面的物件。有了 Lambda 表達式,一切都變得更加簡單。每個 lambda 表達式都可以隱式綁定到某個函數介面。例如,您可以建立對Runnable介面的引用,如下列範例所示:
Runnable r = () -> System.out.println("hello world");
當我們不指定函數介面時,這種轉換總是隱式完成的:
new Thread(
    () -> System.out.println("hello world")
).start();
在上面的範例中,編譯器會自動建立一個 lambda 表達式作為Runnable類別建構函式中介面的實作Threadpublic Thread(Runnable r) { }。以下是 lambda 表達式和對應函數介面的一些範例:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
@FunctionalInterfaceJava 8 根據 Java 語言規格新增的註解檢查宣告的 介面是否有效。此外,Java 8 還包含許多現成的函數接口,可與 Lambda 表達式一起使用。@FunctionalInterface如果聲明的介面不起作用,則會拋出編譯錯誤。以下是定義函數式介面的範例:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
如定義所示,函數式介面只能有一個抽象方法。如果您嘗試新增另一個抽象方法,您將收到編譯錯誤。例子:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
錯誤
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
結論:
Worker вызван через анонимный класс
Worker вызван через Lambda
這裡我們定義了自己的函數式介面並使用了 lambda 表達式。此方法execute()能夠接受 lambda 表達式作為參數。

Lambda 表達式的範例

理解 Lambda 表達式的最佳方法是看幾個範例: 流Thread可以用兩種方式初始化:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Java 8 中的事件管理也可以透過 Lambda 運算式來完成。以下是將事件處理程序新增ActionListener至 UI 元件的兩種方法:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
顯示給定數組的所有元素的簡單範例。請注意,使用 lambda 表達式的方法不只一種。下面我們使用箭頭語法以通常的方式建立一個 lambda 表達式,並且我們也使用雙冒號運算符(::),它在 Java 8 中將常規方法轉換為 lambda 表達式:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
在下面的範例中,我們使用函數式介面Predicate來建立測試並列印通過該測試的項目。透過這種方式,您可以將邏輯放入 lambda 表達式中並基於它執行操作。
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
結論:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
透過修改 Lambda 表達式,您可以顯示清單中每個元素的平方。請注意,我們正在使用該方法stream()將常規清單轉換為流。Java 8 提供了一個很棒的類別Stream( java.util.stream.Stream)。它包含大量可以使用 lambda 表達式的有用方法。我們將 lambda 表達式傳遞x -> x*x給 method map(),該方法將其應用於流中的所有元素。之後我們用來forEach列印列表的所有元素。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
給定一個列表,您需要列印列表中所有元素的平方和。Lambda 表達式讓您只需編寫一行程式碼即可實現此目的。本例使用卷積(約簡)方法reduce()。我們使用一種方法map()對每個元素求平方,然後使用一種方法reduce()將所有元素折疊成一個數字。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

Lambda表達式與匿名類別的區別

主要區別在於關鍵字的使用this。對於匿名類,「 」關鍵字this表示匿名類別的對象,而在 lambda 表達式中,「this」表示使用 lambda 表達式的類別的對象。另一個區別是它們的編譯方式。Java 編譯 lambda 表達式並將其轉換為private類別方法。這使用了Java 7 中引入的invokedynamic指令,用於動態方法綁定。Tal Weiss 在他的部落格中描述了 Java 如何將 lambda 表達式編譯為字節碼

結論

Mark Reinhold(Oracle 首席架構師)稱 Lambda 表達式是程式設計模型中有史以來最重大的變化 - 甚至比泛型更重要。他一定是對的,因為… 它們為 Java 程式設計師提供了每個人都在等待的函數式程式語言功能。除了虛擬擴展方法等創新之外,Lambda 表達式還允許您編寫非常高品質的程式碼。我希望這篇文章能讓您深入了解 Java 8。祝您好運:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION