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();
}
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 Timer
melaksanakan Runnable
antara muka, jadi ia mesti mengisytiharkan dalam dirinya sendiri semua kaedah yang ada dalam Runnable
antara muka dan melaksanakannya, iaitu menulis kod dalam badan kaedah. Begitu juga dengan Calendar
kelas.
Tetapi kini Runnable
pembolehubah boleh menyimpan rujukan kepada objek yang melaksanakan Runnable
antara muka.
Contoh:
Kod | Catatan |
---|---|
|
Kaedah run() dalam Timer kelas akan dipanggil Kaedah run() dalam Timer kelas akan dipanggil Kaedah run() dalam Calendar kelas 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 Timer
dan Calendar
, terdapat dua jenis sedemikian: Object
dan Runnable
.
Jika anda menetapkan rujukan objek kepada Object
pembolehubah, anda hanya boleh memanggil kaedah yang diisytiharkan dalam Object
kelas. Dan jika anda menetapkan rujukan objek kepada pembolehubah Runnable
, anda boleh memanggil kaedah Runnable
jenis 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 Timer
dan Calendar
objek 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, Runnable
antara 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 compareTo
kaedah () 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, Collections
kelas 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?
Comparator
antara 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 Comparator
merupakan 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);
}
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();
}
}
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();
}
}
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);
}
}
Anda boleh mencipta objek yang melaksanakan Comparator
antara 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();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
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();
}
};
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();
}
};
Nampaknya tiada perkara penting yang ditinggalkan. Sesungguhnya, jika Comparator
antara 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 comparator
pembolehubah dengan nilai yang diberikan kepada comparator
pembolehubah.
Taipkan inferens
Tetapi bukan itu sahaja. Kod dalam contoh ini boleh ditulis dengan lebih padat. Pertama, pengkompil boleh menentukan sendiri bahawa pembolehubah obj1
dan obj2
adalah 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)
GO TO FULL VERSION