Java最初是一種完全物件導向的語言。除了原始型別之外,Java 中的一切都是物件。甚至連陣列也是物件。每個類別的實例都是物件。不存在單獨定義函數的可能性(在類別之外 -近似翻譯)。並且無法將方法作為參數傳遞或傳回方法體作為另一個方法的結果。就像那樣。但這是在 Java 8 之前的事。 從古老的 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
類別建構函式中介面的實作Thread
:public 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 };
@FunctionalInterface
Java 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 表達式編譯為字節碼
GO TO FULL VERSION