CodeGym /Java Blog /Random-IT /Interfaccia del comparatore di Java
John Squirrels
Livello 41
San Francisco

Interfaccia del comparatore di Java

Pubblicato nel gruppo Random-IT
I pigri non sono gli unici a scrivere di comparatori e confronti in Java. Non sono pigro, quindi per favore ama e lamentati di un'altra spiegazione. Spero che non sarà superfluo. E sì, questo articolo è la risposta alla domanda: " Puoi scrivere un comparatore a memoria? " Spero che tutti saranno in grado di scrivere un comparatore a memoria dopo aver letto questo articolo. Interfaccia Javas Comparator - 1

introduzione

Come sai, Java è un linguaggio orientato agli oggetti. Di conseguenza, è consuetudine manipolare oggetti in Java. Ma prima o poi, devi affrontare il compito di confrontare gli oggetti in base a qualche caratteristica. Ad esempio : supponiamo di avere un messaggio descritto dalla Messageclasse:

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;
    }
}
Metti questa classe nel compilatore Java Tutorialspoint . Non dimenticare di aggiungere anche le dichiarazioni di importazione:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Nel mainmetodo, crea diversi messaggi:

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);
}
Pensiamo a cosa faremmo se volessimo confrontarli? Ad esempio, vogliamo ordinare per id. E per creare un ordine, dobbiamo in qualche modo confrontare gli oggetti per capire quale oggetto dovrebbe venire prima (cioè quello più piccolo) e quale dovrebbe seguire (cioè quello più grande). Iniziamo con una classe come java.lang.Object . Sappiamo che tutte le classi ereditano implicitamente la Objectclasse. E questo ha senso perché riflette il concetto che "tutto è un oggetto" e fornisce un comportamento comune a tutte le classi. Questa classe impone che ogni classe abbia due metodi: → hashCode Il hashCodemetodo restituisce alcuni valori numerici (int) rappresentazione dell'oggetto. Che cosa significa? Significa che se crei due diverse istanze di una classe, allora dovrebbero avere diversi hashCodes. La descrizione del metodo dice tanto: "Per quanto ragionevolmente pratico, il metodo hashCode definito dalla classe Object restituisce numeri interi distinti per oggetti distinti". In altre parole, per due instances differenti, dovrebbero esserci hashCodes differenti. Cioè, questo metodo non è adatto per il nostro confronto. → equals. Il equalsmetodo risponde alla domanda "questi oggetti sono uguali?" e restituisce un boolean." Per impostazione predefinita, questo metodo ha il seguente codice:

public boolean equals(Object obj) {
    return (this == obj);
}
Cioè, se questo metodo non viene sovrascritto, essenzialmente dice se i riferimenti all'oggetto corrispondono o meno. Questo non è ciò che vogliamo per i nostri messaggi, perché siamo interessati agli ID dei messaggi, non ai riferimenti agli oggetti. E anche se scavalchiamo il equalsmetodo, il massimo che possiamo sperare è sapere se sono uguali. E questo non ci basta per determinare l'ordine. Allora di cosa abbiamo bisogno? Abbiamo bisogno di qualcosa che confronti. Quello che confronta è un Comparator. Apri l' API Java e trova Comparator . In effetti, c'è java.util.Comparatorun'interfaccia java.util.Comparator and java.util.Comparable Come puoi vedere, esiste una tale interfaccia. Una classe che lo implementa dice: "Implemento un metodo che confronta gli oggetti". L'unica cosa che devi davvero ricordare è il contratto di confronto, che è espresso come segue:

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
Ora scriviamo un comparatore. Avremo bisogno di importare java.util.Comparator. Dopo l'istruzione import, aggiungi quanto segue al mainmetodo: Comparator<Message> comparator = new Comparator<Message>(); Naturalmente, questo non funzionerà, perché Comparatorè un'interfaccia. Quindi aggiungiamo le parentesi graffe {}dopo le parentesi. Scrivere il seguente metodo all'interno delle parentesi graffe:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Non hai nemmeno bisogno di ricordare l'ortografia. Un comparatore è colui che esegue un confronto, cioè confronta. Per indicare l'ordine relativo degli oggetti, restituiamo un int. Fondamentalmente è così. Bello e facile. Come puoi vedere dall'esempio, oltre a Comparator, c'è un'altra interfaccia — java.lang.Comparable, che ci richiede di implementare il compareTometodo. Questa interfaccia dice: "una classe che mi implementa rende possibile confrontare le istanze della classe". Ad esempio, Integerl'implementazione di compareTo è la seguente:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 ha introdotto alcune belle modifiche. Se dai un'occhiata più da vicino all'interfaccia Comparator, vedrai l' @FunctionalInterfaceannotazione sopra di essa. Questa annotazione è a scopo informativo e ci dice che questa interfaccia è funzionale. Ciò significa che questa interfaccia ha solo 1 metodo astratto, che è un metodo senza implementazione. Cosa ci dà questo? Ora possiamo scrivere il codice del comparatore in questo modo:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Chiamiamo le variabili tra parentesi. Java vedrà che poiché esiste un solo metodo, il numero e i tipi richiesti di parametri di input sono chiari. Quindi usiamo l'operatore freccia per passarli a questa parte del codice. Inoltre, grazie a Java 8, ora abbiamo metodi predefiniti nelle interfacce. Questi metodi appaiono per impostazione predefinita quando implementiamo un'interfaccia. L' Comparatorinterfaccia ne ha diversi. Per esempio:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
C'è un altro metodo che renderà il tuo codice più pulito. Dai un'occhiata all'esempio sopra, dove abbiamo definito il nostro comparatore. Che cosa fa? È abbastanza primitivo. Prende semplicemente un oggetto ed estrae un valore che è "comparabile". Ad esempio, Integerimplementa comparable, quindi siamo in grado di eseguire un'operazione compareTo sui valori dei campi ID messaggio. Questa semplice funzione di confronto può essere scritta in questo modo:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
In altre parole, abbiamo a Comparatorche confronta in questo modo: prende gli oggetti, usa il getId()metodo per ottenere a Comparableda loro, e poi usa compareToper confrontare. E non ci sono più costrutti orribili. E infine, voglio notare un'altra caratteristica. I comparatori possono essere concatenati. Per esempio:

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

Applicazione

Dichiarare un comparatore risulta abbastanza logico, non credi? Ora dobbiamo vedere come e dove usarlo. → Collections.sort(java.util.Collections) Possiamo, ovviamente, ordinare le raccolte in questo modo. Ma non tutte le collezioni, solo liste. Non c'è niente di insolito qui, perché gli elenchi sono il tipo di raccolte in cui accedi agli elementi in base al loro indice. Ciò consente di scambiare il secondo elemento con il terzo elemento. Ecco perché il seguente metodo di ordinamento è solo per le liste:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Anche gli array sono facili da ordinare. Di nuovo, per lo stesso motivo: i loro elementi sono accessibili tramite index. → Descendants of java.util.SortedSet and java.util.SortedMap Lo ricorderai Sete Mapnon garantisci l'ordine in cui gli elementi sono memorizzati. MA, abbiamo implementazioni speciali che garantiscono l'ordine. E se gli elementi di una collezione non implementano java.util.Comparable, allora possiamo passare a Comparatoral suo costruttore:

Set<Message> msgSet = new TreeSet(comparator);
Stream API Nell'API Stream, apparsa in Java 8, i comparatori consentono di semplificare il lavoro con gli elementi stream. Ad esempio, supponiamo di aver bisogno di una sequenza di numeri casuali da 0 a 999, inclusi:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Potremmo fermarci qui, ma ci sono problemi ancora più interessanti. Ad esempio, supponi di dover preparare un Map, dove la chiave è un ID messaggio. Inoltre, vogliamo ordinare queste chiavi, quindi inizieremo con il seguente codice:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
In realtà otteniamo un HashMapqui. E come sappiamo, non garantisce alcun ordine. Di conseguenza, i nostri elementi, che sono stati ordinati per id, perdono semplicemente il loro ordine. Non bene. Dovremo cambiare un po' il nostro collettore:

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));
Il codice ha iniziato a sembrare un po' più spaventoso, ma ora il problema è stato risolto correttamente. Maggiori informazioni sui vari raggruppamenti qui: Puoi creare il tuo collezionista. Maggiori informazioni qui: "Creazione di un raccoglitore personalizzato in Java 8" . E trarrai vantaggio dalla lettura della discussione qui: "Java 8 list to map with stream" .

Trappola anticaduta

Comparatore Comparablesono buoni. Ma c'è una sfumatura che dovresti ricordare. Quando una classe esegue l'ordinamento, si aspetta che la tua classe possa essere convertita in un file Comparable. In caso contrario, riceverai un errore in fase di esecuzione. Diamo un'occhiata a un esempio:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Sembra che qui non ci sia niente che non va. Ma in realtà, nel nostro esempio, fallirà con un errore: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable E tutto perché ha cercato di ordinare gli elementi (è un SortedSet, dopo tutto)... ma non ci è riuscito. Non dimenticarlo quando si lavora con SortedMape SortedSet.

Altre letture:

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION