CodeGym/Java blogg/Slumpmässig/Javas Comparator-gränssnitt
John Squirrels
Nivå
San Francisco

Javas Comparator-gränssnitt

Publicerad i gruppen
De lata är inte de enda som skriver om komparatorer och jämförelser i Java. Jag är inte lat, så snälla älska och klaga på ännu en förklaring. Jag hoppas att det inte blir överflödigt. Och ja, den här artikeln är svaret på frågan: " Kan du skriva en komparator från minnet? " Jag hoppas att alla kommer att kunna skriva en komparator från minnet efter att ha läst den här artikeln. Javas Comparator-gränssnitt - 1

Introduktion

Som ni vet är Java ett objektorienterat språk. Som ett resultat är det vanligt att manipulera objekt i Java. Men förr eller senare står du inför uppgiften att jämföra objekt utifrån någon egenskap. Till exempel : Anta att vi har något meddelande som beskrivs av Messageklassen:
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;
    }
}
Lägg den här klassen i Tutorialspoint Java-kompilatorn . Glöm inte att lägga till importsatserna också:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
mainSkapa flera meddelanden i metoden:
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);
}
Låt oss fundera på vad vi skulle göra om vi ville jämföra dem? Vi vill till exempel sortera efter id. Och för att skapa en ordning måste vi på något sätt jämföra objekten för att förstå vilket objekt som ska komma först (dvs. det mindre) och vilket som ska följa (dvs. det större). Låt oss börja med en klass som java.lang.Object . Vi vet att alla klasser implicit ärver Objectklassen. Och detta är vettigt eftersom det speglar konceptet att "allt är ett objekt" och ger ett gemensamt beteende för alla klasser. Denna klass dikterar att varje klass har två metoder: → hashCode Metoden hashCodereturnerar några numeriska (int) representation av objektet. Vad betyder det? Det betyder att om du skapar två olika instanser av en klass, så bör de ha olika hashCodes. Metodens beskrivning säger lika mycket: "Så mycket som det är rimligt praktiskt, returnerar hashCode-metoden som definieras av klassen Object distinkta heltal för distinkta objekt". Med andra ord, för två olika instances bör det finnas olika hashCodes. Det vill säga, denna metod är inte lämplig för vår jämförelse. → equals. Metoden equalssvarar på frågan "är dessa objekt lika?" och returnerar ett boolean." Som standard har den här metoden följande kod:
public boolean equals(Object obj) {
    return (this == obj);
}
Det vill säga, om denna metod inte åsidosätts, säger den i huvudsak om objektreferenserna matchar eller inte. Det här är inte vad vi vill ha för våra meddelanden, eftersom vi är intresserade av meddelande-ID, inte objektreferenser. Och även om vi åsidosätter equalsmetoden, är det mest vi kan hoppas på att lära oss om de är lika. Och detta är inte tillräckligt för att vi ska kunna bestämma ordningen. Så vad behöver vi då? Vi behöver något som jämför. Den som jämför är en Comparator. Öppna Java API och hitta Comparator . Det finns faktiskt ett java.util.Comparatorgränssnitt java.util.Comparator and java.util.Comparable Som du kan se finns ett sådant gränssnitt. En klass som implementerar den säger: "Jag implementerar en metod som jämför objekt." Det enda du verkligen behöver komma ihåg är jämförelsekontraktet, som uttrycks så här:
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
Låt oss nu skriva en komparator. Vi kommer att behöva importera java.util.Comparator. Efter importsatsen lägger du till följande till metoden main: Comparator<Message> comparator = new Comparator<Message>(); Naturligtvis kommer detta inte att fungera, eftersom Comparatordet är ett gränssnitt. Så vi lägger till lockiga hängslen {}efter parentesen. Skriv följande metod inuti hängslen:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Du behöver inte ens komma ihåg stavningen. En komparator är en som gör en jämförelse, det vill säga den jämför. För att ange objektens relativa ordning returnerar vi en int. Det är i princip det. Snyggt och enkelt. Som du kan se från exemplet, förutom Comparator, finns det ett annat gränssnitt — , java.lang.Comparablesom kräver att vi implementerar compareTometoden. Detta gränssnitt säger, "en klass som implementerar mig gör det möjligt att jämföra instanser av klassen." Till exempel är Integerimplementeringen av compareTo enligt följande:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 introducerade några trevliga förändringar. Om du tittar närmare på gränssnittet Comparatorser du @FunctionalInterfaceanteckningen ovanför den. Den här anteckningen är avsedd för information och talar om för oss att detta gränssnitt är funktionellt. Detta betyder att detta gränssnitt endast har 1 abstrakt metod, vilket är en metod utan implementering. Vad ger detta oss? Nu kan vi skriva komparatorns kod så här:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Vi namnger variablerna inom parentes. Java kommer att se att eftersom det bara finns en metod är det antal och typer av inmatningsparametrar som krävs är tydliga. Sedan använder vi piloperatorn för att skicka dem till den här delen av koden. Dessutom har vi, tack vare Java 8, nu standardmetoder i gränssnitt. Dessa metoder visas som standard när vi implementerar ett gränssnitt. Gränssnittet Comparatorhar flera. Till exempel:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Det finns en annan metod som gör din kod renare. Ta en titt på exemplet ovan, där vi definierade vår komparator. Vad gör den? Det är ganska primitivt. Den tar helt enkelt ett objekt och extraherar något värde som är "jämförbart". Till exempel Integerimplementerar comparable, så att vi kan utföra en compareTo-operation på värdena för meddelande-id-fält. Denna enkla komparatorfunktion kan skrivas så här:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Med andra ord, vi har en Comparatorsom jämför så här: den tar objekt, använder getId()metoden för att få ett Comparablefrån dem och använder sedan compareToför att jämföra. Och det finns inga fler hemska konstruktioner. Och till sist vill jag notera ytterligare en funktion. Komparatorer kan kedjas. Till exempel:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Ansökan

Att deklarera en komparator visar sig vara ganska logiskt, tycker du inte? Nu måste vi se hur och var vi ska använda det. → Collections.sort(java.util.Collections) Vi kan naturligtvis sortera samlingar på detta sätt. Men inte varje samling, bara listor. Det är inget ovanligt här, eftersom listor är den typen av samlingar där du kommer åt element genom deras index. Detta gör att det andra elementet kan bytas ut mot det tredje elementet. Därför är följande sorteringsmetod endast för listor:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Matriser är också lätta att sortera. Återigen, av samma anledning - deras element nås via index. → Descendants of java.util.SortedSet and java.util.SortedMap Du kommer ihåg det Setoch Mapgaranterar inte i vilken ordning elementen lagras. MEN, vi har speciella implementeringar som garanterar beställningen. Och om en samlings element inte implementerar , java.util.Comparabledå kan vi skicka ett Comparatortill dess konstruktor:
Set<Message> msgSet = new TreeSet(comparator);
Stream API I Stream API, som dök upp i Java 8, låter komparatorer dig förenkla arbetet med streamelement. Anta till exempel att vi behöver en sekvens av slumptal från 0 till 999, inklusive:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Vi skulle kunna sluta här, men det finns ännu mer intressanta problem. Anta till exempel att du behöver förbereda en , Mapdär nyckeln är ett meddelande-id. Dessutom vill vi sortera dessa nycklar, så vi börjar med följande kod:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Vi får faktiskt en HashMaphär. Och som vi vet garanterar det ingen ordning. Som ett resultat förlorar våra element, som sorterades efter id, helt enkelt sin ordning. Inte bra. Vi måste ändra vår samlare lite:
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));
Koden har börjat se lite läskigare ut, men nu är problemet rätt löst. Läs mer om de olika grupperingarna här: Du kan skapa din egen samlare. Läs mer här: "Skapa en anpassad samlare i Java 8" . Och du kommer att ha nytta av att läsa diskussionen här: "Java 8-lista att kartlägga med stream" .

Fall-fälla

Comparatoroch Comparableär bra. Men det finns en nyans du bör komma ihåg. När en klass utför sortering förväntar den sig att din klass kan konverteras till en Comparable. Om så inte är fallet kommer du att få ett felmeddelande vid körning. Låt oss titta på ett exempel:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Det verkar som att inget är fel här. Men faktiskt, i vårt exempel, kommer det att misslyckas med ett fel: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Och allt för att det försökte sortera elementen (det är SortedSettrots allt en )...men kunde inte. Glöm inte detta när du arbetar med SortedMapoch SortedSet.

Mer läsning:

Kommentarer
  • Populär
  • Ny
  • Gammal
Du måste vara inloggad för att lämna en kommentar
Den här sidan har inga kommentarer än