Ciao! Oggi parleremo del confronto degli oggetti. Hmm... Ma sembra che ne abbiamo già parlato più di una volta? :/ Sappiamo come
==
funziona l'operatore “ ”, così come i metodi equals()
e hashCode()
. Il confronto non riguarda proprio questo. In precedenza, intendevamo più come “testare l’uguaglianza degli oggetti”. Ma confrontare gli oggetti tra loro può avere obiettivi completamente diversi! Il più ovvio è l'ordinamento. Penso che se ti viene detto di ordinare un elenco di ArrayList<>
numeri o stringhe, puoi gestirlo senza problemi:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
String name1 = "Masha";
String name2 = "Sasha";
String name3 = "Даша";
List<String> names = new ArrayList<>();
names.add(name1);
names.add(name2);
names.add(name3);
Collections.sort(names);
System.out.println(names);
}
}
Uscita console:
[Даша, Маша, Саша]
È fantastico se ricordi la classe Collections
e il suo metodo sort()
. Non credo che ci saranno problemi nemmeno con i numeri. Ecco un compito più difficile per te:
public class Car {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
//...геттеры, сеттеры, toString()
}
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
}
}
È molto semplice: una classe Car
e 3 dei suoi oggetti. Sii così gentile da ordinare le auto nell'elenco! Probabilmente ti chiederai: “Come dovrebbero essere ordinati?” Per nome, per anno di produzione, per velocità massima? Ottima domanda. Non sappiamo al momento come ordinare gli oggetti della classe Car
. E, naturalmente, anche Java non lo sa! Quando proviamo a passare Collections.sort()
un elenco di oggetti a un metodo Car
, riceveremo un errore:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
//ошибка компилятора!
Collections.sort(cars);
}
}
E davvero, come fa il linguaggio a sapere esattamente come ordinare gli oggetti che scrivi? Dipende dagli obiettivi del tuo programma. Dobbiamo in qualche modo insegnare a Java a confrontare questi oggetti. E confronta il modo in cui ne abbiamo bisogno. A questo scopo Java dispone di uno speciale strumento di interfaccia Comparable
. In inglese questo si traduce con “comparabile”. Affinché i nostri oggetti Car
possano essere confrontati tra loro e in qualche modo ordinati, la classe deve implementare questa interfaccia e implementare il suo unico metodo: compareTo()
:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return 0;
}
//...геттеры, сеттеры, toString()
}
Fai attenzione:abbiamo specificato l'interfaccia Comparable<Car>
, non solo Comparable
. Questa è un'interfaccia tipizzata, ovvero richiede di specificare la classe specifica a cui è associata. In linea di principio <Car>
è possibile rimuoverlo dall'interfaccia, ma poi confronta gli oggetti per impostazione predefinita Object
. Invece di un metodo compareTo(Car o)
nella nostra classe avremo:
@Override
public int compareTo(Object o) {
return 0;
}
Naturalmente per noi è molto più semplice lavorare con i file Car
. All'interno del metodo compareTo()
implementiamo la logica per il confronto delle macchine. Diciamo che dobbiamo ordinarli per anno di produzione. Probabilmente hai notato che il metodo compareTo()
restituisce il valore int
, non boolean
. Non lasciare che questo ti sorprenda. Il fatto è che confrontare due oggetti ci offre 3 possibili opzioni:
а < b
a > b
a == b
.
boolean
solo 2 valori: vero e falso, il che è scomodo per confrontare gli oggetti. Tutto è int
molto più semplice. Se il valore restituito > 0
è , allora a > b
. Se il risultato compareTo < 0
è , allora а < b
. Ebbene, se il risultato è == 0
, allora i due oggetti sono uguali: a == b
. Insegnare alla nostra classe a ordinare le auto per anno di produzione è facile come sgusciare le pere:
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
Cosa sta succedendo qui? Prendiamo un oggetto automobile ( this
), l'anno di produzione di questa automobile, e sottraiamo da esso l'anno di fabbricazione di un'altra automobile (quella con cui confrontiamo l'oggetto). Se l'anno di produzione della prima vettura è maggiore, il metodo restituirà int > 0
. Ciò significa che un'auto this >
è un'auto о
. Se invece l'anno di produzione della seconda vettura ( о
) è maggiore, allora il metodo restituirà un numero negativo, e quindi о > this
. Bene, se sono uguali, il metodo restituirà 0
. Un meccanismo così semplice è già sufficiente per ordinare raccolte di oggetti Car
! Non è necessario fare nient'altro. Ecco:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
//тут раньше была ошибка
Collections.sort(cars);
System.out.println(cars);
}
}
Uscita console:
[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350},
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
Le auto sono ordinate come dovrebbero essere! :) In quali casi dovrebbe essere utilizzato Comparable
? Il metodo di confronto implementato Comparable
è chiamato “ordinamento naturale”. Questo perché nel metodo compareTo()
descrivi il metodo di confronto più comune che verrà utilizzato per gli oggetti di questa classe nel tuo programma. L'ordinamento naturale è già presente in Java. Ad esempio, Java sa che le stringhe sono spesso ordinate in ordine alfabetico e che i numeri sono spesso ordinati in base al valore crescente. Pertanto, se chiami il metodo su un elenco di numeri o stringhe sort()
, questi verranno ordinati. Se nel nostro programma le auto verranno nella maggior parte dei casi confrontate e ordinate per anno di produzione, allora vale la pena definire per loro un ordinamento naturale utilizzando l'interfaccia Comparable<Car>
e il metodo compareTo()
. Ma cosa succede se questo non ci basta? Immaginiamo che il nostro programma non sia così semplice. Nella maggior parte dei casi, l'ordinamento naturale delle auto (lo impostiamo per anno di produzione) è adatto a noi. Ma a volte tra i nostri clienti ci sono gli appassionati di guida veloce. Se stiamo preparando un catalogo di auto tra cui scegliere, è necessario ordinarle in base alla velocità massima. Ad esempio, abbiamo bisogno di tale ordinamento nel 15% dei casi. Ciò chiaramente non è sufficiente per impostare l'ordinamento naturale in base Car
alla velocità anziché all'anno di produzione. Ma non possiamo ignorare il 15% dei clienti. Cosa facciamo? Qui un'altra interfaccia viene in nostro aiuto: Comparator
. Proprio come , Comparable
è digitato. Qual è la differenza? Comparable
rende i nostri oggetti "comparabili" e crea per essi l'ordinamento più naturale che verrà utilizzato nella maggior parte dei casi. Comparator
- questa è una classe "comparatore" separata (la traduzione è un po' goffa, ma comprensibile). Se dobbiamo implementare un ordinamento specifico, non dobbiamo entrare nella classe Car
e cambiare la logica compareTo()
. Possiamo invece creare una classe comparatrice separata nel nostro programma e insegnarle a eseguire l'ordinamento di cui abbiamo bisogno!
import java.util.Comparator;
public class MaxSpeedCarComparator implements Comparator<Car> {
@Override
public int compare(Car o1, Car o2) {
return o1.getMaxSpeed() - o2.getMaxSpeed();
}
}
Come puoi vedere, il nostro Comparator
è abbastanza semplice. Esiste un solo metodo compare()
: si tratta di un metodo di interfaccia Comparator
che deve essere implementato. Prende due oggetti come input Car
e confronta la loro velocità massima nel solito modo (per sottrazione). Ad esempio compareTo()
, restituisce il numero int
, il principio di confronto è lo stesso. Come possiamo usarlo? Molto semplice:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
Comparator speedComparator = new MaxSpeedCarComparator();
Collections.sort(cars, speedComparator);
System.out.println(cars);
}
}
Uscita console:
[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290},
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
Creiamo semplicemente un oggetto comparatore e lo passiamo al metodo Collections.sort()
insieme alla lista da ordinare. Avendo ricevuto un comparatore in input, il metodo sort()
non utilizzerà l'ordinamento naturale definito nel metodo compareTo()
della classe Car
. Applicherà invece l'algoritmo di ordinamento del comparatore che gli è stato passato. Quali vantaggi ci dà questo? Innanzitutto, la compatibilità con il codice scritto. Abbiamo creato un nuovo metodo di ordinamento specifico, pur mantenendo quello attuale, che verrà utilizzato nella maggior parte dei casi. Non abbiamo toccato affatto la classe Car
. E’ Comparable
rimasto com’era:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
//...геттеры, сеттеры, toString()
}
In secondo luogo, la flessibilità. Possiamo aggiungere tutti i tipi che vogliamo. Ad esempio, ordina le auto per colore, velocità, peso o quante volte l'auto è stata utilizzata nei film di Batman. È sufficiente crearne uno aggiuntivo Comparator
. È tutto! Oggi hai imparato due meccanismi molto importanti che utilizzerai spesso in progetti reali sul lavoro. Ma, come sai, la teoria senza la pratica non è nulla. Pertanto, è tempo di consolidare le tue conoscenze e risolvere diversi problemi! :)
GO TO FULL VERSION