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

例子:

代碼 筆記
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

run()類中的方法將Timer被調用類中的方法


將被調用類中的方法將被調用 run()Timer


run()Calendar

您始終可以將對象引用分配給任何類型的變量,只要該類型是對象的祖先類之一。對於TimerandCalendar類,有兩種這樣的類型:Objectand 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();

這段代碼可以工作,因為TimerCalendar對象具有運行良好的運行方法。所以,打電話給他們不是問題。如果我們只是為這兩個類都添加了一個 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變量obj2Strings. 其次,如果方法代碼中只有一個命令,也可以省略花括號和返回語句。

縮短的版本將是這樣的:

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)