1.接口
要了解什麼是 lambda 函數,您首先需要了解什麼是接口。那麼,讓我們回顧一下要點。
接口是類概念的變體。比方說,一個被嚴重截斷的班級。與類不同,接口不能有自己的變量(靜態變量除外)。您也不能創建類型為接口的對象:
- 您不能聲明類的變量
- 你不能創建對象
例子:
interface Runnable
{
void run();
}
使用界面
那麼為什麼需要接口呢?接口只與繼承一起使用。同一個接口可以被不同的類繼承,或者也可以說——類實現了接口。
如果一個類實現了一個接口,它必須實現接口聲明但沒有實現的方法。例子:
interface Runnable
{
void run();
}
class Timer implements Runnable
{
void run()
{
System.out.println(LocalTime.now());
}
}
class Calendar implements Runnable
{
void run()
{
var date = LocalDate.now();
System.out.println("Today: " + date.getDayOfWeek());
}
}
該類Timer
實現Runnable
接口,因此它必須在自身內部聲明接口中的所有方法Runnable
並實現它們,即在方法體中編寫代碼。班級也是如此Calendar
。
但是現在Runnable
變量可以存儲對實現接口的對象的引用Runnable
。
例子:
代碼 | 筆記 |
---|---|
|
run() 類中的方法將Timer 被調用類中的方法將被調用類中的方法將被調用 run() Timer run() Calendar |
您始終可以將對象引用分配給任何類型的變量,只要該類型是對象的祖先類之一。對於Timer
andCalendar
類,有兩種這樣的類型:Object
and Runnable
。
如果將對象引用分配給Object
變量,則只能調用Object
類中聲明的方法。如果將對象引用分配給Runnable
變量,則可以調用該Runnable
類型的方法。
示例 2:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
這段代碼可以工作,因為Timer
和Calendar
對象具有運行良好的運行方法。所以,打電話給他們不是問題。如果我們只是為這兩個類都添加了一個 run() 方法,那麼我們將無法以如此簡單的方式調用它們。
基本上,Runnable
接口只是用作放置運行方法的地方。
2.排序
讓我們繼續做一些更實際的事情。例如,讓我們看一下對字符串進行排序。
為了按字母順序對字符串集合進行排序,Java 有一個很棒的方法叫做Collections.sort(collection);
此靜態方法對傳遞的集合進行排序。並且在排序過程中,它對其元素進行成對比較,以了解元素是否應該交換。
在排序期間,這些比較是使用compareTo
() 方法執行的,所有標準類都有:Integer
, String
, ...
Integer 類的 compareTo() 方法比較兩個數字的值,而 String 類的 compareTo() 方法查看字符串的字母順序。
因此,數字集合將按升序排序,而字符串集合將按字母順序排序。
替代排序算法
但是,如果我們不想按字母順序而是按長度對字符串進行排序怎麼辦?如果我們想按降序對數字進行排序怎麼辦?在這種情況下你會怎麼做?
為了處理這種情況,該類Collections
有另一個sort()
方法有兩個參數:
Collections.sort(collection, comparator);
其中比較器是一個特殊對象,它知道如何在排序操作期間比較集合中的對象。comparator 一詞來自英文單詞comparator,而後者又派生自compare,意思是“比較”。
那麼這個特殊的對像是什麼?
Comparator
界面
好吧,這一切都非常簡單。sort()
該方法的第二個參數的類型是Comparator<T>
其中 T 是一個類型參數,指示集合中元素的類型,並且Comparator
是一個具有單一方法的接口int compare(T obj1, T obj2);
換句話說,比較器對像是實現 Comparator 接口的任何類的任何對象。Comparator 接口看起來很簡單:
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
該compare()
方法比較傳遞給它的兩個參數。
如果該方法返回負數,則表示obj1 < obj2
. 如果該方法返回正數,則表示obj1 > obj2
. 如果該方法返回 0,則表示obj1 == obj2
.
下面是一個按長度比較字符串的比較器對象的示例:
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
StringLengthComparator
班級代碼
要比較字符串長度,只需從另一個中減去一個長度。
按長度對字符串進行排序的程序的完整代碼如下所示:
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Collections.sort(list, new StringLengthComparator());
}
}
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
3.語法糖
你覺得怎麼樣,這段代碼能不能寫得更緊湊一些?基本上,只有一行包含有用的信息 — obj1.length() - obj2.length();
。
但是代碼不能存在於方法之外,所以我們必須添加一個compare()
方法,並且為了存儲方法我們必須添加一個新類 — StringLengthComparator
。而且我們還需要指定變量的類型……一切似乎都是正確的。
但是有一些方法可以使這段代碼更短。我們為您準備了一些語法糖。兩勺!
匿名內部類
您可以在方法內部編寫比較器代碼main()
,編譯器將完成剩下的工作。例子:
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Collections.sort(list, comparator);
}
}
您可以創建一個實現Comparator
接口的對象,而無需顯式創建類!編譯器會自動創建它並給它一些臨時名稱。讓我們比較一下:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
StringLengthComparator
班級
在兩種不同的情況下,相同的顏色用於指示相同的代碼塊。在實踐中差異很小。
當編譯器遇到第一個代碼塊時,它會簡單地生成相應的第二個代碼塊,並為該類賦予一些隨機名稱。
4. Java中的Lambda表達式
假設您決定在代碼中使用匿名內部類。在這種情況下,您將擁有如下代碼塊:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
這裡我們將變量的聲明與匿名類的創建結合起來。但是有一種方法可以使這段代碼更短。例如,像這樣:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
分號是必需的,因為這裡我們不僅有一個隱式類聲明,還有一個變量的創建。
像這樣的表示法稱為lambda 表達式。
如果編譯器在您的代碼中遇到這樣的符號,它只會生成詳細版本的代碼(帶有匿名內部類)。
請注意,在編寫 lambda 表達式時,我們不僅省略了類名,還省略了方法名。Comparator<String>
int compare()
編譯在確定方法時沒有問題,因為只能為具有單一方法的接口編寫 lambda 表達式。順便說一下,有一種方法可以繞過這條規則,但是當您開始更深入地研究 OOP 時,您就會了解到這一點(我們正在談論默認方法)。
讓我們再次看一下詳細版本的代碼,但是我們將在編寫 lambda 表達式時可以省略的部分變灰:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
似乎沒有遺漏任何重要的東西。事實上,如果Comparator
接口只有一個compare()
方法,編譯器可以從剩餘代碼中完全恢復灰色代碼。
排序
順便說一句,現在我們可以這樣寫排序代碼了:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
或者甚至像這樣:
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
我們只是立即comparator
用分配給comparator
變量的值替換了變量。
類型推斷
但這還不是全部。這些示例中的代碼可以寫得更緊湊。首先,編譯器可以自行確定 和obj1
變量obj2
是Strings
. 其次,如果方法代碼中只有一個命令,也可以省略花括號和返回語句。
縮短的版本將是這樣的:
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
如果comparator
我們不使用變量,而是立即使用它的值,那麼我們將得到以下版本:
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
嗯,你怎麼看?只有一行代碼,沒有多餘的信息——只有變量和代碼。沒有辦法讓它變短!或者有嗎?
5. 它是如何運作的
事實上,代碼可以寫得更緊湊。但稍後會詳細介紹。
您可以編寫一個 lambda 表達式,其中您將使用一個接口類型和一個方法。
例如,在代碼中,你可以寫一個 lambda 表達式,因為方法的簽名是這樣的:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
當我們將集合ArrayList<String>
作為第一個參數傳遞給排序方法時,編譯器能夠確定第二個參數的類型是. 並由此得出結論,這個接口只有一個方法。其他一切都是技術問題。Comparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION