這篇文章適合誰?
例如,您可以在Oracle 教學中閱讀更多內容。這稱為“目標打字”。您可以為變數指定任何名稱,不一定是介面中指定的名稱。如果沒有參數,則只需括號。如果只有一個參數,則只是變數名,不帶括號。我們已經整理了參數,現在介紹 lambda 表達式本身的主體。在大括號內,像常規方法一樣編寫程式碼。如果您的整個程式碼僅由一行組成,則根本不必編寫花括號(就像 if 和循環一樣)。如果您的 lambda 傳回某些內容,但其主體由一行組成,則
- 對於那些認為自己已經很了解 Java Core,但對 Java 中的 lambda 表達式一無所知的人。或者,也許您已經聽說過有關 lambda 的一些內容,但沒有了解詳細資訊。
- 適合那些對 lambda 表達式有一定了解,但仍然害怕和不習慣使用它們的人。
- 對物件導向程式設計(以下簡稱OOP)的理解,即:
- 了解什麼是類別和對象,它們之間有什麼區別;
- 了解介面是什麼、它們與類別有何不同、它們之間的聯繫(介面和類別)是什麼;
- 了解什麼是方法,如何呼叫它,什麼是抽象方法(或沒有實現的方法),方法的參數/參數是什麼,如何將它們傳遞到那裡;
- 存取修飾符、靜態方法/變數、最終方法/變數;
- 繼承(類別、介面、介面的多重繼承)。
- Java 核心知識:泛型、集合(列表)、執行緒。
一點歷史
Lambda 表達式源自函數式程式設計和數學。20世紀中葉的美國,有一位在普林斯頓大學工作的阿朗佐·丘奇(Alonzo Church),非常喜歡數學和各種抽象概念。lambda 演算是 Alonzo Church 提出的,起初它只是一些抽象概念的集合,與程式設計無關。同時,阿蘭·圖靈和約翰·馮·諾依曼等數學家也在同一所普林斯頓大學工作。一切都走到了一起:丘奇提出了 lambda 演算系統,圖靈開發了他的抽象計算機,現在被稱為「圖靈機」。馮·諾依曼提出了電腦體系結構圖,它構成了現代電腦的基礎(現在稱為「馮·諾依曼體系結構」)。當時,阿朗佐·丘奇的思想並沒有像他的同事們的工作那樣享有盛譽(“純粹”數學領域除外)。然而,不久之後,約翰·麥卡錫(也是普林斯頓大學的畢業生,在故事發生時是麻省理工學院的員工)對丘奇的想法產生了興趣。基於它們,他於 1958 年創建了第一種函數式程式語言 Lisp。58年後,函數式程式設計的想法以數字8的形式滲透到Java中。甚至還不到70年……事實上,這並不是數學思想在實踐中應用的最長時期。精華
lambda 表達式就是這樣一個函數。您可以將其視為 Java 中的常規方法,唯一的區別是它可以作為參數傳遞給其他方法。是的,不僅可以將數字、字串和貓傳遞給方法,還可以傳遞其他方法!我們什麼時候可能需要它?例如,如果我們想傳遞一些回調。我們需要我們所呼叫的方法能夠呼叫我們傳遞給它的其他方法。也就是說,這樣我們就有機會在某些情況下傳輸一個回調,而在其他情況下傳輸另一個回調。我們的方法將接受回調並調用它們。一個簡單的例子就是排序。假設我們寫了某種棘手的排序,如下所示:
public void mySuperSort() {
// ... do something here
if(compare(obj1, obj2) > 0)
// ... and here we do something
}
在哪裡,if
我們呼叫方法compare()
,傳遞兩個我們比較的對象,我們想找出這些對像中哪一個「更大」。我們將把「多」放在「小」之前。我在引號中寫了“更多”,因為我們正在編寫一個通用方法,它不僅能夠按升序排序,還能夠按降序排序(在這種情況下,“更多”將是本質上更小的對象,反之亦然) 。為了準確地設定我們想要的排序規則,我們需要以某種方式將其傳遞給我們的mySuperSort()
. 在這種情況下,我們將能夠在呼叫我們的方法時以某種方式「控制」它。當然,您可以編寫兩個單獨的方法mySuperSortAsc()
來mySuperSortDesc()
按升序和降序排序。或在方法內部傳遞一些參數(例如,boolean
if true
,按升序排序, if ,按false
降序排序)。但是,如果我們想要排序的不是一些簡單的結構,而是字串數組列表,該怎麼辦?我們的方法如何mySuperSort()
知道如何對這些字串陣列進行排序?要尺寸嗎?按單字總長度?也許按字母順序排列,取決於數組中的第一行?但是,如果在某些情況下,我們需要按數組的大小對數組列表進行排序,而在另一種情況下,則需要按數組中單字的總長度對數組列表進行排序,該怎麼辦?我想您已經聽說過比較器,在這種情況下,我們只需將比較器物件傳遞給我們的排序方法,在該方法中我們描述我們想要排序的規則。由於標準方法sort()
的實作原理與標準方法相同,因此mySuperSort()
在範例中我將使用標準方法sort()
。
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};
List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);
Comparator<String[]> sortByLength = new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
};
Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
int length1 = 0;
int length2 = 0;
for (String s : o1) {
length1 += s.length();
}
for (String s : o2) {
length2 += s.length();
}
return length1 - length2;
}
};
arrays.sort(sortByLength);
結果:
- 媽媽洗了相框
- 和平工黨可能
- 我真的很喜歡java
sort()
我們將另一個比較器傳遞給方法(sortByWordsLength)
,那麼結果將會不同:
- 和平工黨可能
- 媽媽洗了相框
- 我真的很喜歡java
sort()
。像那樣:
String[] array1 = {"Mother", "soap", "frame"};
String[] array2 = {"I", "Very", "I love", "java"};
String[] array3 = {"world", "work", "May"};
List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);
arrays.sort(new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
});
結果將與第一種情況相同。 任務1。重寫此範例,以便它不按數組中單字數的升序對數組進行排序,而是按降序排序。這一切我們都已經知道了。我們知道如何將物件傳遞給方法,我們可以根據我們當前的需要將這個或那個物件傳遞給方法,並且在我們傳遞這樣一個物件的方法內部,我們為其編寫實作的方法將會被呼叫。問題來了:lambda 表達式與它有什麼關係? 假設 lambda 是僅包含一個方法的物件。它就像一個方法物件。包裝在物件中的方法。它們只是有一個稍微不尋常的語法(稍後會詳細介紹)。 讓我們再看一下這個條目
arrays.sort(new Comparator<String[]>() {
@Override
public int compare(String[] o1, String[] o2) {
return o1.length - o2.length;
}
});
在這裡,我們獲取列表arrays
並呼叫其方法sort()
,其中我們透過一個方法傳遞一個比較器物件compare()
(它的名稱對我們來說並不重要,因為它是該物件中唯一的一個,我們不會錯過它)。這個方法需要兩個參數,我們接下來將使用這兩個參數。如果您使用IntelliJ IDEA,您可能已經看到它如何為您提供此程式碼以顯著縮短:
arrays.sort((o1, o2) -> o1.length - o2.length);
就這樣,六行變成了短短的一行。6行重寫為一小段。有些東西消失了,但我保證沒有任何重要的東西消失,並且此程式碼的運作方式與匿名類別完全相同。 任務2。弄清楚如何使用 lambda 重寫問題 1 的解決方案(作為最後的手段,請要求IntelliJ IDEA將您的匿名類別轉換為 lambda)。
我們來談談介面
基本上,介面只是抽象方法的列表。當我們創建一個類別並說它將實現某種介面時,我們必須在類別中編寫介面中列出的方法的實現(或者,作為最後的手段,不編寫它,而是使類別抽象)。有的介面具有多種不同的方法(例如List
),有的介面僅具有一種方法(例如,相同的 Comparator 或 Runnable)。有些介面根本沒有單一方法(所謂的標記接口,例如 Serialized)。那些只有一種方法的介面也稱為函數式介面。在 Java 8 中,它們甚至用特殊的@FunctionalInterface註解進行標記。它是具有適合 lambda 表達式使用的單一方法的介面。正如我上面所說,lambda 表達式是包裝在物件中的方法。當我們在某處傳遞這樣一個物件時,實際上,我們傳遞的是一個方法。事實證明,這個方法叫什麼對我們來說並不重要。對我們來說重要的是該方法所採用的參數,實際上是方法程式碼本身。lambda 表達式本質上是。功能介面的實作。當我們看到一個只有一個方法的介面時,這意味著我們可以使用 lambda 重寫這樣的匿名類別。如果介面有多於/少於一個方法,那麼 lambda 表達式將不適合我們,我們將使用匿名類,甚至是常規類。是時候深入研究 lambda 了。:)
句法
一般語法是這樣的:
(параметры) -> {тело метода}
也就是說,括號內是方法參數,一個「箭頭」(這是連續的兩個字元:減號和大於號),之後方法的主體像往常一樣放在花括號中。參數與描述方法時在介面中指定的參數相對應。如果編譯器可以清楚地定義變數的類型(在我們的例子中,可以肯定我們正在使用字串數組,因為它是List
由字串數組精確鍵入的),那麼變數的類型String[]
不需要被寫下來。
如果不確定,請指定類型,如果不需要,IDEA 將以灰色突出顯示。 |
return
根本不需要編寫。但如果你有花括號,那麼,像通常的方法一樣,你需要顯式地寫return
.
例子
範例 1。() -> {}
最簡單的選擇。而且是最無意義的:).因為它什麼也沒做。 範例 2.
() -> ""
也是一個有趣的選擇。它不接受任何內容並傳回一個空字串(return
因為不必要而被省略)。相同,但具有return
:
() -> {
return "";
}
範例 3. 使用 lambda 的 Hello world
() -> System.out.println("Hello world!")
不接收任何內容,也不返回任何內容(我們不能放在return
call 之前System.out.println()
,因為方法中的返回類型println() — void)
只是在螢幕上顯示一個銘文。非常適合實現介面Runnable
。相同的範例更完整:
public class Main {
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello world!")).start();
}
}
或者像這樣:
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("Hello world!"));
t.start();
}
}
或者我們甚至可以將 lambda 表達式儲存為 type 的對象Runnable
,然後將其傳遞給建構函數thread’а
:
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("Hello world!");
Thread t = new Thread(runnable);
t.start();
}
}
讓我們仔細看看將 lambda 表達式保存到變數中的那一刻。介面Runnable
告訴我們它的物件必須有一個方法public void run()
。根據接口,run 方法不接受任何參數作為參數。它不會返回任何東西(void)
。因此,以這種方式編寫時,將使用某種不接受或傳回任何內容的方法來建立一個物件。run()
與介面中的方法非常一致Runnable
。這就是為什麼我們能夠將此 lambda 表達式放入諸如 之類的變數中Runnable
。 實施例4
() -> 42
同樣,它不接受任何內容,但返回數字 42。這個 lambda 表達式可以放置在 類型的變數中Callable
,因為這個介面只定義了一個方法,它看起來像這樣:
V call(),
其中V
是傳回值的類型(在我們的例子中int
)。因此,我們可以儲存這樣的 lambda 表達式,如下所示:
Callable<Integer> c = () -> 42;
範例 5. 多行中的 Lambda
() -> {
String[] helloWorld = {"Hello", "world!"};
System.out.println(helloWorld[0]);
System.out.println(helloWorld[1]);
}
同樣,這是一個沒有參數及其返回類型的 lambda 表達式void
(因為沒有return
)。 實施例6
x -> x
在這裡,我們將一些內容放入變數中х
並返回它。請注意,如果只接受一個參數,則不需要寫括號。相同,但有括號:
(x) -> x
這是一個明確的選項return
:
x -> {
return x;
}
或者像這樣,用括號和return
:
(x) -> {
return x;
}
或明確指示類型(並相應地使用括號):
(int x) -> x
實施例7
x -> ++x
我們接受х
並退回,但要求1
更多。你也可以這樣重寫:
x -> x + 1
在這兩種情況下,我們都不在參數、方法體和單字周圍指示括號return
,因為這是不必要的。範例 6 中描述了帶括號和回車的選項。 範例 8
(x, y) -> x % y
我們接受一些х
並返回除以у
的餘數。這裡已經需要參數周圍的括號。只有當只有一個參數時,它們才是可選的。像這樣明確指示類型: x
y
(double x, int y) -> x % y
實施例9
(Cat cat, String name, int age) -> {
cat.setName(name);
cat.setAge(age);
}
我們接受一個 Cat 物件、一個帶有名稱和整數年齡的字串。在方法本身中,我們將傳遞的名稱和年齡設為 Cat。cat
由於我們的變數是引用類型,因此 lambda 表達式外部的 Cat 物件將會發生變化(它將接收內部傳遞的姓名和年齡)。使用類似 lambda 的稍微複雜的版本:
public class Main {
public static void main(String[] args) {
// create a cat and print to the screen to make sure it's "blank"
Cat myCat = new Cat();
System.out.println(myCat);
// create lambda
Settable<Cat> s = (obj, name, age) -> {
obj.setName(name);
obj.setAge(age);
};
// call the method, to which we pass the cat and the lambda
changeEntity(myCat, s);
// display on the screen and see that the state of the cat has changed (has a name and age)
System.out.println(myCat);
}
private static <T extends WithNameAndAge> void changeEntity(T entity, Settable<T> s) {
s.set(entity, "Murzik", 3);
}
}
interface WithNameAndAge {
void setName(String name);
void setAge(int age);
}
interface Settable<C extends WithNameAndAge> {
void set(C entity, String name, int age);
}
class Cat implements WithNameAndAge {
private String name;
private int age;
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
結果: Cat{name='null',age=0} Cat{name='Murzik',age=3} 可以看到,一開始 Cat 物件只有一個狀態,但使用 lambda 表達式後,狀態發生了變化。Lambda 表達式與泛型配合得很好。例如,如果我們需要建立一個類別Dog
,它也將實現WithNameAndAge
,那麼在方法中main()
我們可以對 Dog 執行相同的操作,而無需更改 lambda 表達式本身。 任務3。使用接受數字並返回布林值的方法編寫函數介面。以 lambda 表達式的形式編寫此類介面的實現,true
如果傳遞的數字可以被 13 整除而沒有餘數,則傳回該 表達式。任務 4。使用接受兩個字串並傳回相同字串的方法編寫一個函數介面。以傳回最長字串的 lambda 形式編寫此類介面的實作。 任務5。使用接受三個小數的方法寫一個函數介面:a
、b
、c
並傳回相同的小數。以傳回判別式的 lambda 表達式的形式編寫此類介面的實作。誰忘記了,D = b^2 - 4ac。 任務 6 . 使用任務 5 中的函數式接口,編寫一個返回運算結果的 lambda 表達式a * b^c
。 Java 中流行的 lambda 表達式。帶有範例和任務。第2部分。
GO TO FULL VERSION