CodeGym /Java Blog /Willekeurig /Java's Comparator-interface
John Squirrels
Niveau 41
San Francisco

Java's Comparator-interface

Gepubliceerd in de groep Willekeurig
De luie mensen zijn niet de enigen die over Comparators en vergelijkingen in Java schrijven. Ik ben niet lui, dus hou alsjeblieft van en klaag over nog een andere uitleg. Ik hoop dat het niet overbodig zal zijn. En ja, dit artikel is het antwoord op de vraag: " Kun je een comparator uit het geheugen schrijven? " Ik hoop dat iedereen na het lezen van dit artikel in staat zal zijn om een ​​comparator uit het geheugen te schrijven. Javas Comparator-interface - 1

Invoering

Zoals u weet, is Java een objectgeoriënteerde taal. Als gevolg hiervan is het gebruikelijk om objecten in Java te manipuleren. Maar vroeg of laat sta je voor de taak om objecten te vergelijken op basis van een bepaald kenmerk. Bijvoorbeeld : Stel dat we een bericht hebben dat wordt beschreven door de Messageklasse:

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;
    }
}
Zet deze klasse in de Tutorialspoint Java-compiler . Vergeet niet om ook de importverklaringen toe te voegen:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
mainMaak in de methode verschillende berichten:

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);
}
Laten we eens nadenken over wat we zouden doen als we ze wilden vergelijken? We willen bijvoorbeeld sorteren op id. En om een ​​volgorde te creëren, moeten we op de een of andere manier de objecten vergelijken om te begrijpen welk object eerst moet komen (dwz de kleinere) en welke moet volgen (dwz de grotere). Laten we beginnen met een klasse zoals java.lang.Object . We weten dat alle klassen impliciet de Objectklasse erven. En dit is logisch omdat het het concept weerspiegelt dat "alles een object is" en gemeenschappelijk gedrag voor alle klassen biedt. Deze klasse schrijft voor dat elke klasse twee methoden heeft: → hashCode De hashCodemethode retourneert een aantal numerieke (int) weergave van het object. Wat betekent dat? Het betekent dat als je twee verschillende instanties van een klasse maakt, ze verschillende hashCodes moeten hebben. De beschrijving van de methode zegt zoveel: "Zoveel als redelijk praktisch is, retourneert de hashCode-methode gedefinieerd door klasse Object verschillende gehele getallen voor verschillende objecten". Met andere woorden, voor twee verschillende instances zouden er verschillende hashCodes moeten zijn. Dat wil zeggen, deze methode is niet geschikt voor onze vergelijking. → equals. De equalsmethode beantwoordt de vraag "zijn deze objecten gelijk?" en retourneert een boolean." Deze methode heeft standaard de volgende code:

public boolean equals(Object obj) {
    return (this == obj);
}
Dat wil zeggen, als deze methode niet wordt overschreven, zegt deze in wezen of de objectverwijzingen overeenkomen of niet. Dit is niet wat we willen voor onze berichten, omdat we geïnteresseerd zijn in bericht-ID's, niet in objectreferenties. En zelfs als we de equalsmethode terzijde schuiven, is het enige waarop we kunnen hopen te leren of ze gelijk zijn. En dit is niet genoeg voor ons om de volgorde te bepalen. Dus wat hebben we dan nodig? We hebben iets nodig dat vergelijkbaar is. Degene die vergelijkt is een Comparator. Open de Java API en zoek Comparator . Er is inderdaad een java.util.Comparatorinterface java.util.Comparator and java.util.Comparable Zoals u kunt zien, bestaat zo'n interface. Een klasse die het implementeert, zegt: "Ik implementeer een methode die objecten vergelijkt." Het enige dat u echt moet onthouden, is het vergelijkingscontract, dat als volgt wordt uitgedrukt:

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
Laten we nu een comparator schrijven. We zullen moeten importeren java.util.Comparator. Voeg na de importopdracht het volgende toe aan de mainmethode: Comparator<Message> comparator = new Comparator<Message>(); Natuurlijk werkt dit niet, omdat Comparatorhet een interface is. Dus voegen we accolades toe {}na de haakjes. Schrijf de volgende methode tussen de accolades:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Je hoeft de spelling niet eens te onthouden. Een vergelijker is iemand die een vergelijking uitvoert, dat wil zeggen, hij vergelijkt. Om de relatieve volgorde van de objecten aan te geven, geven we een int. Dat is het eigenlijk. Leuk en gemakkelijk. Zoals u in het voorbeeld kunt zien, is er naast Comparator nog een interface — java.lang.Comparable, waarvoor we de methode moeten implementeren compareTo. Deze interface zegt: "een klasse die mij implementeert, maakt het mogelijk om instanties van de klasse te vergelijken." IntegerDe implementatie van compareTo is bijvoorbeeld als volgt:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 heeft een aantal leuke veranderingen geïntroduceerd. Als u de interface beter bekijkt Comparator, ziet u de @FunctionalInterfaceannotatie erboven. Deze annotatie is voor informatieve doeleinden en vertelt ons dat deze interface functioneel is. Dit betekent dat deze interface maar 1 abstracte methode heeft en dat is een methode zonder implementatie. Wat levert dit ons op? Nu kunnen we de code van de vergelijker als volgt schrijven:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
We noemen de variabelen tussen haakjes. Java zal zien dat, omdat er maar één methode is, het vereiste aantal en type invoerparameters duidelijk is. Vervolgens gebruiken we de pijloperator om ze door te geven aan dit deel van de code. Bovendien hebben we dankzij Java 8 nu standaardmethoden in interfaces. Deze methoden verschijnen standaard wanneer we een interface implementeren. De Comparatorinterface heeft er meerdere. Bijvoorbeeld:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Er is een andere methode die uw code schoner maakt. Bekijk het bovenstaande voorbeeld, waar we onze comparator hebben gedefinieerd. Wat doet het? Het is nogal primitief. Het neemt gewoon een object en extraheert een waarde die "vergelijkbaar" is. IntegerImplements bijvoorbeeld comparable, zodat we een vergelijkTo-bewerking kunnen uitvoeren op de waarden van bericht-ID-velden. Deze eenvoudige vergelijkingsfunctie kan als volgt worden geschreven:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Met andere woorden, we hebben een Comparatordie als volgt vergelijkt: er zijn objecten voor nodig, gebruikt de getId()methode om Comparableer een uit te halen en gebruikt vervolgens compareToom te vergelijken. En er zijn geen vreselijke constructies meer. En tot slot wil ik nog een kenmerk opmerken. Vergelijkers kunnen worden geketend. Bijvoorbeeld:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Sollicitatie

Het declareren van een comparator blijkt vrij logisch, vind je niet? Nu moeten we kijken hoe en waar we het kunnen gebruiken. → Collections.sort(java.util.Collections) We kunnen collecties natuurlijk op deze manier sorteren. Maar niet elke verzameling, alleen lijsten. Er is hier niets ongewoons, omdat lijsten het soort verzamelingen zijn waar je toegang krijgt tot elementen op basis van hun index. Hierdoor kan het tweede element worden verwisseld met het derde element. Daarom is de volgende sorteermethode alleen voor lijsten:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Arrays zijn ook gemakkelijk te sorteren. Nogmaals, om dezelfde reden: hun elementen zijn toegankelijk via index. → Descendants of java.util.SortedSet and java.util.SortedMap U herinnert zich dat Seten Mapgarandeert niet de volgorde waarin elementen worden opgeslagen. MAAR, we hebben speciale uitvoeringen die de bestelling wel garanderen. En als de elementen van een verzameling niet implementeren java.util.Comparable, kunnen we een doorgeven Comparatoraan de constructor:

Set<Message> msgSet = new TreeSet(comparator);
Stream API In de Stream API, die verscheen in Java 8, kunt u met vergelijkers het werken met stream-elementen vereenvoudigen. Stel dat we een reeks willekeurige getallen van 0 tot en met 999 nodig hebben:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
We zouden hier kunnen stoppen, maar er zijn nog meer interessante problemen. Stel dat u een moet voorbereiden Map, waarbij de sleutel een bericht-ID is. Bovendien willen we deze sleutels sorteren, dus we beginnen met de volgende code:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
We krijgen eigenlijk een HashMaphier. En zoals we weten, garandeert het geen enkele bestelling. Als gevolg hiervan verliezen onze elementen, die op id zijn gesorteerd, gewoon hun volgorde. Niet goed. We zullen onze verzamelaar een beetje moeten veranderen:

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));
De code is er wat enger uit gaan zien, maar nu is het probleem correct opgelost. Lees hier meer over de verschillende groepen: U kunt uw eigen verzamelprogramma maken. Lees hier meer: ​​"Een aangepast verzamelprogramma maken in Java 8" . En u zult profiteren van het lezen van de discussie hier: "Java 8-lijst om in kaart te brengen met stream" .

Valstrik

Comparatoren Comparablezijn goed. Maar er is één nuance die u moet onthouden. Wanneer een klasse sorteert, verwacht deze dat uw klasse kan worden geconverteerd naar een Comparable. Als dit niet het geval is, krijgt u tijdens runtime een foutmelding. Laten we naar een voorbeeld kijken:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Het lijkt erop dat hier niets aan de hand is. Maar in feite zal het in ons voorbeeld mislukken met een fout: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable En dat allemaal omdat het probeerde de elementen te sorteren (het is SortedSettenslotte een )...maar dat lukte niet. Vergeet dit niet bij het werken met SortedMapen SortedSet.
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION