CodeGym/Java blog/Véletlen/A Java Comparator felülete
John Squirrels
Szint
San Francisco

A Java Comparator felülete

Megjelent a csoportban
Nem csak a lusták írnak a Java komparátorokról és összehasonlításokról. Nem vagyok lusta, ezért kérlek, szeress és bánkódj egy újabb magyarázatért. Remélem nem lesz felesleges. És igen, ez a cikk a válasz arra a kérdésre: " Tudsz-e emlékezetből komparátort írni? " Remélem, a cikk elolvasása után mindenki emlékezetből tud majd összehasonlítót írni. Javas Comparator felület - 1

Bevezetés

Mint tudják, a Java egy objektum-orientált nyelv. Ennek eredményeként a Java-ban megszokott objektumok manipulálása. De előbb-utóbb azzal a feladattal kell szembenéznie, hogy az objektumokat valamilyen jellemző alapján hasonlítsa össze. Például : Tegyük fel, hogy van egy üzenetünk, amelyet az Messageosztály ír le:
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;
    }
}
Helyezze ezt az osztályt a Tutorialspoint Java fordítójába . Ne felejtse el hozzáadni az import utasításokat is:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
A módszerben mainhozzon létre több üzenetet:
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);
}
Gondoljuk végig, mit tennénk, ha össze akarnánk hasonlítani őket? Például azonosító szerint szeretnénk rendezni. A sorrend létrehozásához pedig valahogy össze kell hasonlítanunk az objektumokat, hogy megértsük, melyik objektum legyen előbb (azaz a kisebb), és melyik következzen (azaz a nagyobb). Kezdjük egy olyan osztállyal, mint a java.lang.Object . Tudjuk, hogy minden osztály implicit módon örökli az Objectosztályt. És ennek van értelme, mert tükrözi azt a koncepciót, hogy "minden egy tárgy", és minden osztály számára közös viselkedést biztosít. Ez az osztály azt írja elő, hogy minden osztálynak két metódusa legyen: → hashCode A hashCodemetódus bizonyos numerikus (int) az objektum ábrázolása. Az mit jelent? Ez azt jelenti, hogy ha egy osztály két különböző példányát hoz létre, akkor azoknak különböző hashCodes-ekkel kell rendelkezniük. A módszer leírása annyit mond: "Amennyire ésszerűen praktikus, az Object osztály által meghatározott hashCode metódus különálló egész számokat ad vissza a különböző objektumokhoz". Más szavakkal, két különböző instances-hez különböző s-nek kell lennie hashCode. Vagyis ez a módszer nem alkalmas az összehasonlításunkra. → equals. A equalsmódszer választ ad arra a kérdésre, hogy "ezek az objektumok egyenlőek?" és a következőt adja vissza boolean." Alapértelmezés szerint ez a metódus a következő kóddal rendelkezik:
public boolean equals(Object obj) {
    return (this == obj);
}
Vagyis ha ez a metódus nincs felülírva, akkor lényegében megmondja, hogy az objektumhivatkozások egyeznek-e vagy sem. Nem ezt akarjuk az üzeneteinkhez, mert minket az üzenetazonosítók érdekelnek, nem az objektum-hivatkozások. És még ha felülírjuk is a equalsmódszert, a legtöbb, amit remélhetünk, hogy megtudjuk, egyenlőek-e. És ez nem elég ahhoz, hogy meghatározzuk a sorrendet. Akkor mire van szükségünk? Szükségünk van valami összehasonlításra. Aki összehasonlít, az egy Comparator. Nyissa meg a Java API-t , és keresse meg a Comparator alkalmazást . Valóban, van java.util.Comparatorinterfész java.util.Comparator and java.util.Comparable Mint látható, létezik ilyen interfész. Az ezt megvalósító osztály azt mondja: "Olyan metódust valósítok meg, amely összehasonlítja az objektumokat." Az egyetlen dolog, amire igazán emlékeznie kell, az az összehasonlító szerződés, amely a következőképpen fejeződik ki:
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
Most írjunk egy összehasonlítót. Importálnunk kell java.util.Comparator. Az import utasítás után adjuk hozzá a metódushoz a következőket main: Comparator<Message> comparator = new Comparator<Message>(); Természetesen ez nem fog működni, mert Comparatorez egy interfész. {}Így a zárójelek után göndör kapcsos zárójelet adunk . Írja be a következő módszert a kapcsos zárójelbe:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Még a helyesírásra sem kell emlékezned. Összehasonlító az, aki összehasonlítást végez, azaz összehasonlít. Az objektumok egymáshoz viszonyított sorrendjének jelzésére egy -et adunk vissza int. Alapvetően ennyi. Szépen lassan. Amint a példából is látható, a Comparator mellett van egy másik interfész — , amely megköveteli a metódus java.lang.Comparablemegvalósítását . compareToEz az interfész azt mondja: "az engem megvalósító osztály lehetővé teszi az osztály példányainak összehasonlítását." Például Integera To megvalósítása comparea következő:
(x < y) ? -1 : ((x == y) ? 0 : 1)
A Java 8 néhány szép változást vezetett be. Ha közelebbről megnézi a Comparatorfelületet, láthatja @FunctionalInterfacefelette a megjegyzést. Ez a megjegyzés tájékoztató jellegű, és azt mondja, hogy ez a felület működőképes. Ez azt jelenti, hogy ennek az interfésznek csak 1 absztrakt metódusa van, ami egy implementáció nélküli metódus. Mit ad ez nekünk? Most a következőképpen írhatjuk fel az összehasonlító kódot:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
A változókat zárójelben nevezzük el. A Java látni fogja, hogy mivel csak egy módszer létezik, a bemeneti paraméterek szükséges száma és típusa egyértelmű. Ezután a nyíl operátorral továbbítjuk őket a kód ezen részére. Ráadásul a Java 8-nak köszönhetően mostantól alapértelmezett metódusaink vannak az interfészekben. Ezek a metódusok alapértelmezés szerint megjelennek, amikor interfészt implementálunk. A Comparatorfelület több. Például:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Van egy másik módszer, amely tisztábbá teszi a kódot. Vessen egy pillantást a fenti példára, ahol meghatároztuk a komparátorunkat. Mit csinal? Elég primitív. Egyszerűen vesz egy objektumot, és kivon egy "összehasonlítható" értéket. Például Integerimplements comparable, így egy Összehasonlítás műveletet tudunk végrehajtani az üzenetazonosító mezők értékein. Ezt az egyszerű összehasonlító függvényt így írhatjuk fel:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Más szóval, van egy Comparator, amely így hasonlít össze: objektumokat vesz fel, a getId()metódus segítségével lekéri Comparablebelőlük a-t, majd compareToösszehasonlítja. És nincs több szörnyű konstrukció. És végül még egy jellemzőt szeretnék megjegyezni. Az összehasonlítók láncolhatók. Például:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Alkalmazás

A komparátor bejelentése egészen logikusnak bizonyul, nem gondolod? Most meg kell néznünk, hogyan és hol használjuk. → Collections.sort(java.util.Collections) A gyűjteményeket természetesen rendezhetjük így is. De nem minden gyűjtemény, csak listák. Nincs itt semmi szokatlan, mert a listák olyan gyűjtemények, ahol az elemeket indexük alapján érheti el. Ez lehetővé teszi a második elem felcserélését a harmadik elemmel. Ezért a következő rendezési mód csak listákra vonatkozik:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) A tömbök is könnyen rendezhetők. Ugyanebből az okból kifolyólag – elemeik index által érhetők el. → Descendants of java.util.SortedSet and java.util.SortedMap Emlékszel erre, Setés Mapnem garantálja az elemek tárolási sorrendjét. DE vannak speciális megvalósításaink, amelyek garantálják a rendelést. És ha egy gyűjtemény elemei nem valósulnak meg java.util.Comparable, akkor átadhatjuk a Comparatorkonstruktorának:
Set<Message> msgSet = new TreeSet(comparator);
Stream API A Java 8-ban megjelent Stream API-ban az összehasonlítók lehetővé teszik a streamelemekkel való munka egyszerűsítését. Tegyük fel például, hogy szükségünk van egy véletlen számsorra 0 és 999 között, beleértve:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Itt megállhatnánk, de vannak még érdekesebb problémák. Tegyük fel például, hogy elő kell készítenie egy Map, ahol a kulcs egy üzenetazonosító. Ezenkívül rendezni szeretnénk ezeket a kulcsokat, ezért a következő kóddal kezdjük:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Valójában itt kapunk egyet HashMap. És mint tudjuk, ez nem garantál semmilyen megrendelést. Ennek eredményeként az id alapján rendezett elemeink egyszerűen elveszítik sorrendjüket. Nem jó. Kicsit változtatnunk kell a gyűjtőn:
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));
A kód kezdett egy kicsit ijesztőbbnek tűnni, de most a probléma megfelelően megoldódott. A különböző csoportosításokról itt olvashat bővebben: Létrehozhatja saját gyűjtőjét. Bővebben itt: "Egyéni gyűjtő létrehozása Java 8-ban" . Hasznos lehet, ha elolvassa az itt található vitát: "A Java 8 listája a streameléshez" .

Eséscsapda

Comparatorés Comparablejók. De van egy árnyalat, amelyet emlékeznie kell. Amikor egy osztály rendezést hajt végre, arra számít, hogy az osztály átalakítható Comparable. Ha nem ez a helyzet, akkor futás közben hibaüzenetet fog kapni. Nézzünk egy példát:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Úgy tűnik, nincs itt semmi baj. De valójában a mi példánkban ez egy hibával meghiúsul: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable És mindez azért, mert megpróbálta rendezni az elemeket (végül is egy SortedSet), de nem sikerült. Ne felejtse el ezt, amikor a SortedMapés -vel dolgozik SortedSet.

További olvasnivalók:

Hozzászólások
  • Népszerű
  • Új
  • Régi
Hozzászólás írásához be kell jelentkeznie
Ennek az oldalnak még nincsenek megjegyzései