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.

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 azMessage
osztá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 main
hozzon 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 Object
osztá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 hashCode
metó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ő hashCode
s-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ő instance
s-hez különböző s-nek kell lennie hashCode
. Vagyis ez a módszer nem alkalmas az összehasonlításunkra. → equals
. A equals
mó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 equals
mó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.Comparator
interfé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 Comparator
ez 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.Comparable
megvalósítását . compareTo
Ez 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 Integer
a To megvalósítása compare
a 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 Comparator
felületet, láthatja @FunctionalInterface
felette 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 Comparator
felü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 Integer
implements 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 Comparable
belő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 Map
nem 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 Comparator
konstruktorá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 Comparable
jó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: |
---|
GO TO FULL VERSION