CodeGym/Blog Java/rawak/Antara muka Pembanding Java
John Squirrels
Tahap
San Francisco

Antara muka Pembanding Java

Diterbitkan dalam kumpulan
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. Antara muka Pembanding Javas - 1

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 oleh Messagekelas:
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 mainkaedah, 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 Objectkelas 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 hashCodemengembalikan beberapa angka (int) perwakilan objek. Apakah maksudnya? Ini bermakna jika anda mencipta dua contoh kelas yang berbeza, maka ia sepatutnya mempunyai hashCodes 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 instances yang berbeza, harus ada hashCodes yang berbeza. Maksudnya, kaedah ini tidak sesuai untuk perbandingan kita. → equals. Kaedah equalsmenjawab 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 equalskaedah 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.Comparatorantara 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 mainkaedah: Comparator<Message> comparator = new Comparator<Message>(); Sudah tentu, ini tidak akan berfungsi, kerana Comparatorialah 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 compareTokaedah tersebut. Antara muka ini berkata, "kelas yang melaksanakan saya memungkinkan untuk membandingkan contoh kelas." Sebagai contoh, Integerpelaksanaan compareTo adalah seperti berikut:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 memperkenalkan beberapa perubahan yang bagus. Jika anda melihat dengan lebih dekat pada Comparatorantara muka, anda akan melihat @FunctionalInterfaceanotasi 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 Comparatormuka 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, Integerimplements 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 Comparatoryang membandingkan seperti ini: ia mengambil objek, menggunakan getId()kaedah untuk mendapatkan Comparabledaripada mereka, dan kemudian menggunakan compareTountuk 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 Setdan Maptidak 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 Comparatorkepada 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 HashMapsini. 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

Comparatordan Comparablebaik. 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 SortedMapdan SortedSet.

Bacaan lanjut:

Komen
  • Popular
  • Baru
  • Tua
Anda mesti log masuk untuk meninggalkan ulasan
Halaman ini tidak mempunyai sebarang ulasan lagi