1. Antara muka

Untuk memahami fungsi lambda, anda perlu memahami antara muka terlebih dahulu. Jadi, mari kita ingat perkara utama.

Antara muka ialah variasi konsep kelas. Kelas yang sangat dipotong, katakan. Tidak seperti kelas, antara muka tidak boleh mempunyai pembolehubah sendiri (kecuali yang statik). Anda juga tidak boleh mencipta objek yang jenisnya ialah antara muka:

  • Anda tidak boleh mengisytiharkan pembolehubah kelas
  • Anda tidak boleh mencipta objek

Contoh:

interface Runnable
{
   void run();
}
Contoh antara muka standard

Menggunakan antara muka

Jadi mengapa antara muka diperlukan? Antara muka hanya digunakan bersama dengan warisan. Antara muka yang sama boleh diwarisi oleh kelas yang berbeza, atau seperti yang juga dikatakan — kelas melaksanakan antara muka .

Jika kelas melaksanakan antara muka, ia mesti melaksanakan kaedah yang diisytiharkan oleh tetapi tidak dilaksanakan oleh antara muka. Contoh:

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());
   }
}

Kelas Timermelaksanakan Runnableantara muka, jadi ia mesti mengisytiharkan dalam dirinya sendiri semua kaedah yang ada dalam Runnableantara muka dan melaksanakannya, iaitu menulis kod dalam badan kaedah. Begitu juga dengan Calendarkelas.

Tetapi kini Runnablepembolehubah boleh menyimpan rujukan kepada objek yang melaksanakan Runnableantara muka.

Contoh:

Kod Catatan
Timer timer = new Timer();
timer.run();

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

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

Kaedah run()dalam Timerkelas akan dipanggil


Kaedah run()dalam Timerkelas akan dipanggil


Kaedah run()dalam Calendarkelas akan dipanggil

Anda sentiasa boleh menetapkan rujukan objek kepada pembolehubah apa-apa jenis, asalkan jenis itu ialah salah satu daripada kelas nenek moyang objek. Untuk kelas Timerdan Calendar, terdapat dua jenis sedemikian: Objectdan Runnable.

Jika anda menetapkan rujukan objek kepada Objectpembolehubah, anda hanya boleh memanggil kaedah yang diisytiharkan dalam Objectkelas. Dan jika anda menetapkan rujukan objek kepada pembolehubah Runnable, anda boleh memanggil kaedah Runnablejenis tersebut.

Contoh 2:

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

Kod ini akan berfungsi, kerana Timerdan Calendarobjek telah menjalankan kaedah yang berfungsi dengan baik. Jadi, memanggil mereka tidak menjadi masalah. Jika kami baru sahaja menambah kaedah run() kepada kedua-dua kelas, maka kami tidak akan dapat memanggilnya dengan cara yang begitu mudah.

Pada asasnya, Runnableantara muka hanya digunakan sebagai tempat untuk meletakkan kaedah larian.



2. Menyusun

Mari kita beralih kepada sesuatu yang lebih praktikal. Sebagai contoh, mari lihat rentetan pengisihan.

Untuk mengisih koleksi rentetan mengikut abjad, Java mempunyai kaedah yang hebat dipanggilCollections.sort(collection);

Kaedah statik ini mengisih koleksi yang diluluskan. Dan dalam proses pengisihan, ia melakukan perbandingan berpasangan bagi elemennya untuk memahami sama ada elemen perlu ditukar.

Semasa pengisihan, perbandingan ini dilakukan menggunakan compareTokaedah () yang semua kelas standard mempunyai: Integer, String, ...

Kaedah compareTo() kelas Integer membandingkan nilai dua nombor, manakala kaedah compareTo() kelas String melihat susunan abjad rentetan.

Jadi koleksi nombor akan diisih dalam tertib menaik, manakala koleksi rentetan akan diisih mengikut abjad.

Algoritma pengisihan alternatif

Tetapi bagaimana jika kita mahu mengisih rentetan bukan mengikut abjad, tetapi mengikut panjangnya? Dan bagaimana jika kita ingin mengisih nombor dalam susunan menurun? Apa yang anda lakukan dalam kes ini?

Untuk mengendalikan situasi sedemikian, Collectionskelas mempunyai sort()kaedah lain yang mempunyai dua parameter:

Collections.sort(collection, comparator);

Di mana pembanding ialah objek khas yang tahu cara membandingkan objek dalam koleksi semasa operasi isihan . Istilah comparator berasal daripada perkataan Inggeris comparator , yang seterusnya berasal daripada compare , yang bermaksud "membandingkan".

Jadi apakah objek istimewa ini?

Comparatorantara muka

Nah, semuanya sangat mudah. Jenis sort()parameter kedua kaedah ialahComparator<T>

Di mana T ialah parameter jenis yang menunjukkan jenis elemen dalam koleksi dan Comparatormerupakan antara muka yang mempunyai satu kaedahint compare(T obj1, T obj2);

Dalam erti kata lain, objek pembanding ialah sebarang objek dari mana-mana kelas yang melaksanakan antara muka Pembanding. Antara muka Comparator kelihatan sangat mudah:

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Kod untuk antara muka Pembanding

Kaedah ini compare()membandingkan dua hujah yang dihantar kepadanya.

Jika kaedah mengembalikan nombor negatif, itu bermakna obj1 < obj2. Jika kaedah mengembalikan nombor positif, itu bermakna obj1 > obj2. Jika kaedah mengembalikan 0, itu bermakna obj1 == obj2.

Berikut ialah contoh objek pembanding yang membandingkan rentetan mengikut panjangnya:

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
}
Kod StringLengthComparatorkelas

Untuk membandingkan panjang rentetan, hanya tolak satu panjang daripada yang lain.

Kod lengkap untuk program yang menyusun rentetan mengikut panjang akan kelihatan seperti ini:

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();
   }
}
Menyusun rentetan mengikut panjang


3. Gula sintaksis

Apa pendapat anda, bolehkah kod ini ditulis dengan lebih padat? Pada asasnya, hanya terdapat satu baris yang mengandungi maklumat berguna — obj1.length() - obj2.length();.

Tetapi kod tidak boleh wujud di luar kaedah, jadi kami terpaksa menambah compare()kaedah, dan untuk menyimpan kaedah kami perlu menambah kelas baharu — StringLengthComparator. Dan kita juga perlu menentukan jenis pembolehubah... Semuanya nampaknya betul.

Tetapi ada cara untuk menjadikan kod ini lebih pendek. Kami ada beberapa gula sintaksis untuk anda. Dua sudu!

Kelas dalaman tanpa nama

Anda boleh menulis kod pembanding betul-betul di dalam main()kaedah, dan pengkompil akan melakukan yang lain. Contoh:

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);
    }
}
Isih rentetan mengikut panjang

Anda boleh mencipta objek yang melaksanakan Comparatorantara muka tanpa membuat kelas secara eksplisit! Pengkompil akan menciptanya secara automatik dan memberikannya beberapa nama sementara. Mari bandingkan:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Kelas dalaman tanpa nama
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparatorkelas

Warna yang sama digunakan untuk menunjukkan blok kod yang sama dalam dua kes yang berbeza. Perbezaannya agak kecil dalam amalan.

Apabila pengkompil menemui blok kod pertama, ia hanya menjana blok kod kedua yang sepadan dan memberi kelas beberapa nama rawak.


4. Ungkapan Lambda dalam Java

Katakan anda memutuskan untuk menggunakan kelas dalaman tanpa nama dalam kod anda. Dalam kes ini, anda akan mempunyai blok kod seperti ini:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
Kelas dalaman tanpa nama

Di sini kami menggabungkan pengisytiharan pembolehubah dengan penciptaan kelas tanpa nama. Tetapi ada cara untuk membuat kod ini lebih pendek. Sebagai contoh, seperti ini:

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length() – obj2.length();
};

Titik koma diperlukan kerana di sini kita bukan sahaja mempunyai pengisytiharan kelas tersirat, tetapi juga penciptaan pembolehubah.

Notasi seperti ini dipanggil ungkapan lambda.

Jika pengkompil menemui notasi seperti ini dalam kod anda, ia hanya menjana versi verbose kod (dengan kelas dalaman tanpa nama).

Ambil perhatian bahawa semasa menulis ungkapan lambda, kami tidak hanya meninggalkan nama kelas , tetapi juga nama kaedah .Comparator<String>int compare()

Penyusun tidak akan mempunyai masalah untuk menentukan kaedah , kerana ungkapan lambda boleh ditulis hanya untuk antara muka yang mempunyai satu kaedah . Ngomong-ngomong, terdapat cara untuk mengatasi peraturan ini, tetapi anda akan mengetahui tentangnya apabila anda mula mempelajari OOP dengan lebih mendalam (kita bercakap tentang kaedah lalai).

Mari kita lihat versi verbose kod sekali lagi, tetapi kita akan kelabukan bahagian yang boleh ditinggalkan semasa menulis ungkapan lambda:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length() – obj2.length();
   }
};
Kelas dalaman tanpa nama

Nampaknya tiada perkara penting yang ditinggalkan. Sesungguhnya, jika Comparatorantara muka hanya mempunyai satu compare()kaedah, pengkompil boleh memulihkan sepenuhnya kod yang dikelabukan daripada kod yang tinggal.

Menyusun

Dengan cara ini, sekarang kita boleh menulis kod pengisihan seperti ini:

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);

Atau bahkan seperti ini:

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length() – obj2.length();
   }
);

Kami hanya segera menggantikan comparatorpembolehubah dengan nilai yang diberikan kepada comparatorpembolehubah.

Taipkan inferens

Tetapi bukan itu sahaja. Kod dalam contoh ini boleh ditulis dengan lebih padat. Pertama, pengkompil boleh menentukan sendiri bahawa pembolehubah obj1dan obj2adalah Strings. Dan kedua, kurungan kerinting dan pernyataan kembali juga boleh ditinggalkan jika anda hanya mempunyai satu arahan dalam kod kaedah.

Versi yang dipendekkan adalah seperti ini:

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length() – obj2.length();

Collections.sort(list, comparator);

Dan jika daripada menggunakan pembolehubah comparator, kami segera menggunakan nilainya, maka kami mendapat versi berikut:

Collections.sort(list, (obj1, obj2) ->  obj1.length() — obj2.length() );

Nah, apa pendapat anda tentang itu? Hanya satu baris kod tanpa maklumat yang berlebihan — hanya pembolehubah dan kod. Tidak ada cara untuk menjadikannya lebih pendek! Atau ada?



5. Bagaimana ia berfungsi

Malah, kod itu boleh ditulis dengan lebih padat. Tetapi lebih lanjut mengenai itu kemudian.

Anda boleh menulis ungkapan lambda di mana anda akan menggunakan jenis antara muka dengan satu kaedah.

Sebagai contoh, dalam kod , anda boleh menulis ungkapan lambda kerana tandatangan kaedah adalah seperti ini:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

Apabila kami menyerahkan ArrayList<String>koleksi sebagai hujah pertama kepada kaedah isihan, pengkompil dapat menentukan bahawa jenis hujah kedua ialah . Dan daripada ini, ia menyimpulkan bahawa antara muka ini mempunyai satu kaedah. Segala-galanya adalah teknikal.Comparator<String>int compare(String obj1, String obj2)