Yang malas bukan satu-satunya yang menulis tentang Comparator dan perbandingan di Jawa. Saya tidak malas, jadi sila suka dan merungut tentang penjelasan lain. Saya harap ia tidak akan berlebihan. Dan ya, artikel ini adalah jawapan kepada soalan: " Bolehkah anda menulis pembanding dari ingatan? " Saya harap semua orang akan dapat menulis pembanding dari ingatan selepas membaca artikel ini.

pengenalan
Seperti yang anda ketahui, Java ialah bahasa berorientasikan objek. Akibatnya, adalah kebiasaan untuk memanipulasi objek di Jawa. Tetapi lambat laun, anda menghadapi tugas membandingkan objek berdasarkan beberapa ciri. Sebagai contoh : Katakan kita mempunyai beberapa mesej yang diterangkan olehMessage
kelas:
public static class Message {
private String message;
private int id;
public Message(String message) {
this.message = message;
this.id = new Random().nextInt(1000);
}
public String getMessage() {
return message;
}
public Integer getId() {
return id;
}
public String toString() {
return "[" + id + "] " + message;
}
}
Letakkan kelas ini dalam pengkompil Java Tutorialspoint . Jangan lupa untuk menambah penyata import juga:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Dalam main
kaedah, buat beberapa mesej:
public static void main(String[] args){
List<Message> messages = new ArrayList();
messages.add(new Message("Hello, World!"));
messages.add(new Message("Hello, Sun!"));
System.out.println(messages);
}
Mari kita fikirkan apa yang akan kita lakukan jika kita ingin membandingkannya? Sebagai contoh, kami ingin mengisih mengikut id. Dan untuk membuat pesanan, entah bagaimana kita perlu membandingkan objek untuk memahami objek mana yang harus didahulukan (iaitu yang lebih kecil) dan yang mana harus diikuti (iaitu yang lebih besar). Mari kita mulakan dengan kelas seperti java.lang.Object . Kami tahu bahawa semua kelas secara tersirat mewarisi Object
kelas tersebut. Dan ini masuk akal kerana ia mencerminkan konsep bahawa "semuanya adalah objek" dan menyediakan tingkah laku biasa untuk semua kelas. Kelas ini menentukan bahawa setiap kelas mempunyai dua kaedah: → hashCode
Kaedah hashCode
mengembalikan beberapa angka (int
) perwakilan objek. Apakah maksudnya? Ini bermakna jika anda mencipta dua contoh kelas yang berbeza, maka ia sepatutnya mempunyai hashCode
s yang berbeza. Penerangan kaedah mengatakan sebanyak: "Sebanyak yang munasabah praktikal, kaedah hashCode yang ditakrifkan oleh Objek kelas tidak mengembalikan integer yang berbeza untuk objek yang berbeza". Dalam erti kata lain, untuk dua instance
s yang berbeza, harus ada hashCode
s yang berbeza. Maksudnya, kaedah ini tidak sesuai untuk perbandingan kita. → equals
. Kaedah equals
menjawab soalan "adakah objek ini sama?" dan mengembalikan boolean
." Secara lalai, kaedah ini mempunyai kod berikut:
public boolean equals(Object obj) {
return (this == obj);
}
Iaitu, jika kaedah ini tidak ditindih, ia pada dasarnya mengatakan sama ada rujukan objek sepadan atau tidak. Ini bukan yang kami mahukan untuk mesej kami, kerana kami berminat dengan id mesej, bukan rujukan objek. Dan walaupun kita mengatasi equals
kaedah itu, perkara yang paling kita boleh harapkan adalah untuk mengetahui sama ada mereka sama. Dan ini tidak mencukupi untuk kami menentukan pesanan. Jadi apa yang kita perlukan kemudian? Kami memerlukan sesuatu yang membandingkan. Yang membuat perbandingan ialah seorang Comparator
. Buka API Java dan cari Comparator . Memang ada java.util.Comparator
antara muka java.util.Comparator and java.util.Comparable
Seperti yang anda lihat, antara muka sedemikian wujud. Kelas yang melaksanakannya berkata, "Saya melaksanakan kaedah yang membandingkan objek." Satu-satunya perkara yang anda perlu ingat ialah kontrak pembanding, yang dinyatakan seperti berikut:
Comparator returns an int according to the following rules:
- It returns a negative int if the first object is smaller
- It returns a positive int if the first object is larger
- It returns zero if the objects are equal
Sekarang mari kita tulis pembanding. Kami perlu mengimport java.util.Comparator
. Selepas penyataan import, tambahkan yang berikut pada main
kaedah: Comparator<Message> comparator = new Comparator<Message>();
Sudah tentu, ini tidak akan berfungsi, kerana Comparator
ialah antara muka. Jadi kami menambah kurungan kerinting {}
selepas kurungan. Tulis kaedah berikut di dalam pendakap:
public int compare(Message o1, Message o2) {
return o1.getId().compareTo(o2.getId());
}
Anda tidak perlu mengingati ejaannya. Pembanding ialah orang yang melakukan perbandingan, iaitu membandingkan. Untuk menunjukkan susunan relatif objek, kami kembalikan int
. Itu pada asasnya. Senang dan mudah. Seperti yang anda lihat daripada contoh, sebagai tambahan kepada Comparator, terdapat satu lagi antara muka — java.lang.Comparable
, yang memerlukan kami melaksanakan compareTo
kaedah tersebut. Antara muka ini berkata, "kelas yang melaksanakan saya memungkinkan untuk membandingkan contoh kelas." Sebagai contoh, Integer
pelaksanaan compare
To adalah seperti berikut:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 memperkenalkan beberapa perubahan yang bagus. Jika anda melihat dengan lebih dekat pada Comparator
antara muka, anda akan melihat @FunctionalInterface
anotasi di atasnya. Anotasi ini adalah untuk tujuan maklumat dan memberitahu kami bahawa antara muka ini berfungsi. Ini bermakna antara muka ini hanya mempunyai 1 kaedah abstrak, iaitu kaedah tanpa pelaksanaan. Apa yang diberikan ini kepada kita? Sekarang kita boleh menulis kod pembanding seperti ini:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Kami menamakan pembolehubah dalam kurungan. Java akan melihatnya kerana hanya terdapat satu kaedah, maka nombor dan jenis parameter input yang diperlukan adalah jelas. Kemudian kami menggunakan pengendali anak panah untuk menghantarnya ke bahagian kod ini. Lebih-lebih lagi, terima kasih kepada Java 8, kami kini mempunyai kaedah lalai dalam antara muka. Kaedah ini muncul secara lalai apabila kami melaksanakan antara muka. Antara Comparator
muka mempunyai beberapa. Sebagai contoh:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Terdapat kaedah lain yang akan menjadikan kod anda lebih bersih. Lihat contoh di atas, di mana kami menentukan pembanding kami. Apa yang ia lakukan? Ia agak primitif. Ia hanya mengambil objek dan mengekstrak beberapa nilai yang "setanding". Contohnya, Integer
implements comparable
, jadi kami dapat melaksanakan operasi compareTo pada nilai medan id mesej. Fungsi pembanding mudah ini boleh ditulis seperti ini:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Dalam erti kata lain, kami mempunyai Comparator
yang membandingkan seperti ini: ia mengambil objek, menggunakan getId()
kaedah untuk mendapatkan Comparable
daripada mereka, dan kemudian menggunakan compareTo
untuk membandingkan. Dan tidak ada lagi binaan yang mengerikan. Dan akhirnya, saya ingin perhatikan satu lagi ciri. Komparator boleh dirantai. Sebagai contoh:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());
Permohonan
Mengisytiharkan pembanding ternyata agak logik, bukan? Sekarang kita perlu melihat bagaimana dan di mana untuk menggunakannya. →Collections.sort(java.util.Collections)
Sudah tentu, kita boleh menyusun koleksi dengan cara ini. Tetapi bukan setiap koleksi, hanya senarai. Tiada apa-apa yang luar biasa di sini, kerana senarai ialah jenis koleksi di mana anda mengakses elemen mengikut indeksnya. Ini membolehkan elemen kedua ditukar dengan elemen ketiga. Itulah sebabnya kaedah pengisihan berikut hanya untuk senarai:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
→ Arrays.sort(java.util.Arrays)
Tatasusunan juga mudah diisih. Sekali lagi, atas sebab yang sama - elemen mereka diakses oleh indeks. → Descendants of java.util.SortedSet and java.util.SortedMap
Anda akan mengingatinya Set
dan Map
tidak menjamin susunan elemen disimpan. TETAPI, kami mempunyai pelaksanaan khas yang menjamin pesanan itu. Dan jika elemen koleksi tidak melaksanakan java.util.Comparable
, maka kita boleh menghantar a Comparator
kepada pembinanya:
Set<Message> msgSet = new TreeSet(comparator);
→ Stream API
Dalam API Strim, yang muncul dalam Java 8, pembanding membolehkan anda memudahkan kerja dengan elemen aliran. Sebagai contoh, katakan kita memerlukan urutan nombor rawak dari 0 hingga 999, termasuk:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
.limit(10)
.sorted(Comparator.naturalOrder())
.forEach(e -> System.out.println(e));
Kita boleh berhenti di sini, tetapi terdapat masalah yang lebih menarik. Sebagai contoh, katakan anda perlu menyediakan Map
, dengan kuncinya ialah id mesej. Selain itu, kami ingin mengisih kunci ini, jadi kami akan mulakan dengan kod berikut:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Kami sebenarnya mendapat di HashMap
sini. Dan seperti yang kita tahu, ia tidak menjamin sebarang pesanan. Akibatnya, elemen kami, yang diisih mengikut id, kehilangan susunannya. Tidak baik. Kami perlu menukar sedikit pengumpul kami:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
Kod itu telah mula kelihatan lebih menakutkan, tetapi kini masalahnya diselesaikan dengan betul. Baca lebih lanjut mengenai pelbagai kumpulan di sini:
Anda boleh membuat pengumpul anda sendiri. Baca lebih lanjut di sini: "Mencipta pengumpul tersuai dalam Java 8" . Dan anda akan mendapat manfaat daripada membaca perbincangan di sini: "Senarai Java 8 untuk dipetakan dengan strim" .
Perangkap jatuh
Comparator
dan Comparable
baik. Tetapi ada satu nuansa yang perlu anda ingat. Apabila kelas melakukan pengisihan, ia menjangkakan kelas anda boleh ditukar kepada Comparable
. Jika ini tidak berlaku, maka anda akan menerima ralat pada masa jalankan. Mari lihat contoh:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Nampaknya tiada apa yang salah di sini. Tetapi sebenarnya, dalam contoh kami, ia akan gagal dengan ralat: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable
Dan semua kerana ia cuba mengisih elemen (ia adalah SortedSet
, selepas semua)...tetapi tidak dapat. Jangan lupa ini apabila bekerja dengan SortedMap
dan SortedSet
.
Bacaan lanjut: |
---|
GO TO FULL VERSION