JavaRush /Java Blog /Random-IT /Comparatore in Java
Viacheslav
Livello 3

Comparatore in Java

Pubblicato nel gruppo Random-IT
Solo le persone pigre non hanno scritto di Comparator e del confronto in Java. Non sono pigro, quindi ti chiedo di amare e favorire un'altra variazione. Spero che non sarà superfluo. E sì, questo articolo è la risposta alla domanda: “Sai scrivere un comparatore a memoria?” Spero che dopo aver letto questo articolo tutti saranno in grado di scrivere un comparatore a memoria.
Comparatore in Java - 1
Introduzione Java è noto per essere un linguaggio orientato agli oggetti. Di conseguenza, in Java è comune operare con gli oggetti. Ma prima o poi sorge il compito di confrontare gli oggetti secondo un principio. Quindi, dato: abbiamo un messaggio, che è descritto dalla classe Message:
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;
    }
}
Aggiungiamo questa classe al compilatore Java Tutorialspoint . Ricordiamoci anche di aggiungere le importazioni:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Nel metodo principale creeremo 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 dovremmo fare se vogliamo confrontarli? Ad esempio, vogliamo ordinare per ID. E per creare ordine, è necessario in qualche modo confrontare gli oggetti per capire quale oggetto è precedente (cioè più piccolo) e quale è successivo (cioè più grande). Iniziamo con una classe come java.lang.Object . Come sappiamo, tutte le classi ereditano implicitamente da questa classe Object. E questo è logico, perché Ciò esprime essenzialmente il concetto: "Tutto è un oggetto" e fornisce un comportamento comune per tutte le classi. E questa classe definisce che ogni classe ha due metodi: → hashCode Il metodo hashCode restituisce una rappresentazione numerica (int) dell'oggetto come un'istanza della classe. Cosa significa? Ciò significa che se hai creato due diverse istanze di una classe, poiché le istanze sono diverse, il loro hashCode dovrebbe essere diverso. Questo è ciò che si dice nella descrizione del metodo: "Per quanto ragionevolmente pratico, il metodo hashCode definito dalla classe Object restituisce interi distinti per oggetti distinti" Cioè, se si tratta di due istanze diverse, allora dovrebbero avere valori diversi hashCodes. Cioè, questo metodo non è adatto al nostro confronto. → equals Il metodo equals risponde alla domanda "gli oggetti sono uguali" e restituisce un valore booleano. Questo metodo ha il codice predefinito:
public boolean equals(Object obj) {
    return (this == obj);
}
Cioè, senza sovrascrivere questo metodo su un oggetto, questo metodo dice essenzialmente se i riferimenti all'oggetto corrispondono o meno. Questo non è adatto ai nostri messaggi, perché non ci interessano i collegamenti all'oggetto, ci interessa l'id del messaggio. E anche se sovrascrivessimo il metodo uguale, il massimo che otterremmo sarebbe: “Sono uguali” o “Non sono uguali”. Ma questo non ci basta per determinare l’ordine.

Comparatore e comparabile in Java

Cosa ci conviene? Se traduciamo la parola “confrontare” in inglese nel traduttore, otterremo la traduzione “confrontare”. Ottimo, allora ci serve qualcuno con cui fare paragoni. Se confronti questo confronto, allora colui che confronta è il Comparatore. Apriamo Java Api e troviamo Comparator lì . E in effetti, esiste una tale interfaccia: java.util.Comparator java.util.Comparator e java.lang.Comparable Come puoi vedere, esiste una tale interfaccia. La classe che lo implementa dice che "sto implementando una funzione per confrontare oggetti". L’unica cosa da ricordare veramente è il contratto comparativo, che si esprime così:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Ora scriviamo un comparatore. Dovremo importare java.util.Comparator . Dopo l'importazione aggiungi un metodo a main: Comparator<Message> comparator = new Comparator<Message>(); naturalmente questo non funzionerà, perché Il comparatore è un'interfaccia. Pertanto, dopo le parentesi aggiungeremo quelle ricci { }. Tra queste parentesi scriveremo il metodo:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Non devi nemmeno ricordarti di scriverlo. Un comparatore è colui che effettua un confronto, cioè fa un confronto. Per rispondere alla domanda su quale ordine siano gli oggetti confrontati, restituiamo int. Questo è tutto, in realtà. Semplicemente e facilmente. Come possiamo vedere dall'esempio, oltre a Comparator, c'è un'altra interfaccia - java.lang.Comparable , implementando la quale dobbiamo definire il metodo compareTo . Questa interfaccia dice che "Una classe che implementa un'interfaccia consente il confronto delle istanze della classe". Ad esempio, l'implementazione di compareTo di Integer si presenta così:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Come ricordare tutte queste interfacce? Per che cosa? Tutto viene dall'inglese. Confronta - per confrontare, colui che confronta è Comparatore (come registrar, ad esempio, cioè colui che registra), e l'aggettivo “comparato” è Comparabile. Ebbene, “Confronta con” è tradotto non solo come confronto con, ma anche come confronto con. È semplice. La lingua Java è stata scritta da persone di lingua inglese, e nel dare un nome a tutto in Java erano guidati semplicemente dall'inglese e c'era una sorta di logica nella denominazione. E il metodo compareTo descrive come un'istanza di una classe dovrebbe essere confrontata con altre istanze. Ad esempio, le stringhe vengono confrontate dal punto di vista lessigrafico e i numeri vengono confrontati in base al valore.
Comparatore in Java - 2
Java 8 ha apportato alcune modifiche interessanti. Se osserviamo da vicino l'interfaccia del Comparatore, vedremo che sopra è presente un'annotazione @FunctionalInterface. In effetti, questa annotazione è informativa e significa che questa interfaccia è funzionale. Ciò significa che questa interfaccia ha solo 1 metodo astratto senza implementazione. Cosa ci dà questo? Possiamo scrivere il codice del comparatore ora in questo modo:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Tra parentesi è indicato il nome delle variabili. Java stesso lo vedrà perché... Se esiste un solo metodo, allora è chiaro quali parametri di input sono necessari, quanti e di che tipo. Successivamente, diciamo con una freccia che vogliamo trasferirli in questa sezione del codice. Inoltre, grazie a Java 8, nelle interfacce sono comparsi metodi predefiniti: si tratta di metodi che appaiono per impostazione predefinita (per impostazione predefinita) quando implementiamo un'interfaccia. Ce ne sono diversi nell'interfaccia del Comparatore, ad esempio:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Esiste un altro metodo che renderà il tuo codice più pulito. Diamo un'occhiata all'esempio sopra, in cui abbiamo descritto il nostro comparatore. Cosa sta facendo? È piuttosto primitivo. Prende semplicemente un oggetto e ne estrae un valore comparabile. Ad esempio, Integer implementa il confronto, quindi siamo stati in grado di eseguire compareTo sui valori dell'ID del messaggio. Questa semplice funzione comparatrice può anche essere scritta in questo modo:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Cioè, letteralmente, "Abbiamo un comparatore che effettua il confronto in questo modo: prende oggetti, ottiene comparabile da essi utilizzando il metodo getId(), confronta utilizzando compareTo." E niente più disegni terribili. E infine, vorrei notare un'altra caratteristica. I comparatori possono essere concatenati insieme. Per esempio:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Applicazione

La dichiarazione del comparatore si è rivelata abbastanza logica, non è vero? Ora bisogna vedere come utilizzarlo e in quali luoghi. → Collections.sort (java.util.Collections) Naturalmente possiamo ordinare le raccolte in questo modo. Ma non tutto, solo elenchi. E non c'è niente di insolito qui, perché... È l'elenco che richiede l'accesso a un elemento tramite indice. E questo consente di scambiare l'elemento numero due con l'elemento numero tre. Pertanto l'ordinamento in questo modo è possibile 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 convenienti da ordinare. Ancora una volta, per lo stesso motivo di accesso agli elementi tramite indice. → Discendenti di java.util.SortedSet e java.util.SortedMap Come ricordiamo, Set e Map non garantiscono l'ordine di memorizzazione dei record. MA abbiamo implementazioni speciali che garantiscono l'ordine. E se gli elementi della raccolta non implementano java.lang.Comparable, allora possiamo passare Comparator al costruttore di tali raccolte:
Set<Message> msgSet = new TreeSet(comparator);
Stream API Nella Stream Api, apparsa in Java 8, un comparatore consente di semplificare il lavoro sugli elementi dello stream. Ad esempio, abbiamo 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, ma ci sono problemi più interessanti. Ad esempio, devi preparare una mappa, dove la chiave è l'id del messaggio. Allo stesso tempo, vogliamo ordinare queste chiavi in ​​modo che siano in ordine, dalla più piccola alla più grande. Iniziamo con questo codice:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Ciò che otterremo qui è in realtà una HashMap. E come sappiamo, non garantisce alcun ordine. Pertanto, i nostri record ordinati per ID sono semplicemente andati fuori servizio. Non bene. Dovremo cambiare un po' il nostro raccoglitore:
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 sembrava un po' più inquietante, ma ora il problema è stato risolto correttamente grazie all'implementazione esplicita della TreeMap. Puoi leggere di più sui vari gruppi qui: Puoi creare tu stesso il raccoglitore. Puoi leggere di più qui: "Creazione di un raccoglitore personalizzato in Java 8" . Ed è utile leggere la discussione qui: "Elenco Java 8 da mappare con stream" .
Comparatore in Java - 3
Il comparatore e i rastrelli comparabili sono buoni. Ma c'è una sfumatura ad essi associata che vale la pena ricordare. Quando una classe esegue l'ordinamento, calcola che può trasmettere la tua classe a Comparable. In caso contrario, riceverai un errore al momento dell'esecuzione. Diamo un'occhiata ad un esempio:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Sembra che non ci sia niente di sbagliato qui. Ma in effetti, nel nostro esempio, si bloccherà con l'errore: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable E tutto perché ha tentato di ordinare gli elementi (dopo tutto è un SortedSet). E non potevo. Dovresti ricordarlo quando lavori con SortedMap e SortedSet. Consigliato inoltre per la visualizzazione: Yuri Tkach: HashSet e TreeSet - Collezioni #1 - Java avanzato
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION