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.
Lo ricorderai

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 dallaMessage
classe:
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 main
metodo, 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 Object
classe. 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 hashCode
metodo 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 hashCode
s. 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 instance
s differenti, dovrebbero esserci hashCode
s differenti. Cioè, questo metodo non è adatto per il nostro confronto. → equals
. Il equals
metodo 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 equals
metodo, 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.Comparator
un'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 main
metodo: 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 compareTo
metodo. Questa interfaccia dice: "una classe che mi implementa rende possibile confrontare le istanze della classe". Ad esempio, Integer
l'implementazione di compare
To è 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' @FunctionalInterface
annotazione 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' Comparator
interfaccia 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, Integer
implementa 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 Comparator
che confronta in questo modo: prende gli oggetti, usa il getId()
metodo per ottenere a Comparable
da loro, e poi usa compareTo
per 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
Set
e Map
non 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 Comparator
al 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 HashMap
qui. 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
Comparator
e Comparable
sono 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 SortedMap
e SortedSet
.
Altre letture: |
---|
GO TO FULL VERSION