Hallo! Heute werden wir über den Vergleich von Objekten sprechen. Hmm... Aber haben wir dieses Thema nicht schon mehr als einmal besprochen? :/ Wir kennen die
==
Funktionsweise des Operators sowie die equals()
und- hashCode()
Methoden. Der Vergleich ist etwas anders. Früher meinten wir höchstwahrscheinlich „Objekte auf Gleichheit prüfen“. Doch die Gründe, Objekte miteinander zu vergleichen, können ganz unterschiedlich sein! Das offensichtlichste davon ist das Sortieren. Ich denke, wenn man Ihnen sagen würde, dass Sie eine Reihe ArrayList<>
von Zahlen oder Zeichenfolgen sortieren sollen, könnten Sie dies ohne Probleme bewältigen:
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 = "Dasha";
List<String> names = new ArrayList<>();
names.add(name1);
names.add(name2);
names.add(name3);
Collections.sort(names);
System.out.println(names);
}
}
Konsolenausgabe:
[Dasha, Masha, Sasha]
Wenn Sie sich an die Collections
Klasse und ihre sort()
Methode erinnern, gut gemacht! Ich denke, dass Sie auch mit Zahlen keine Probleme haben werden. Hier ist eine anspruchsvollere Aufgabe für Sie:
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;
}
// ...getters, setters, 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);
}
}
Die Aufgabe ist eigentlich einfach. Wir haben eine Car
Klasse und 3 Autoobjekte. Würden Sie bitte die Autos in der Liste sortieren? Sie werden sich wahrscheinlich fragen: „Wie sollen sie sortiert werden?“ Namentlich? Nach Baujahr? Durch Höchstgeschwindigkeit? Ausgezeichnete Frage. Im Moment wissen wir nicht, wie wir die Car
Objekte sortieren sollen. Und das weiß Java natürlich auch nicht! Car
Wenn wir versuchen, eine Liste von Objekten an die Methode zu übergeben Collections.sort()
, erhalten wir eine Fehlermeldung:
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);
// Compilation error!
Collections.sort(cars);
}
}
Und wie sollte die Sprache tatsächlich wissen, wie sie Objekte der von Ihnen geschriebenen Klassen sortieren soll? Dies hängt davon ab, was Ihr Programm tun muss. Wir müssen Java irgendwie beibringen, diese Objekte zu vergleichen. Und sie genau so zu vergleichen, wie wir es wollen. Java verfügt dafür über einen speziellen Mechanismus: die Comparable
Schnittstelle. Um unsere Car
Objekte irgendwie vergleichen und sortieren zu können, muss die Klasse diese Schnittstelle implementieren, die aus einer einzigen Methode besteht 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;
}
// ...getters, setters, toString()
}
bitte beachten Siedass wir die Comparable<Car>
Schnittstelle angegeben haben, nicht nur Comparable
. Dies ist eine parametrisierte Schnittstelle, das heißt, wir müssen die spezifische zugehörige Klasse angeben. Im Prinzip können Sie sie <Car>
aus der Schnittstelle entfernen, der Vergleich basiert dann jedoch Object
standardmäßig auf Objekten. Anstelle der compareTo(Car o)
Methode wird unsere Klasse Folgendes haben:
@Override
public int compareTo(Object o) {
return 0;
}
Natürlich ist es für uns viel einfacher, damit zu arbeiten Car
. Innerhalb der compareTo()
Methode implementieren wir unsere Logik zum Vergleichen von Autos. Angenommen, wir müssen sie nach Herstellungsjahr sortieren. Sie haben wahrscheinlich bemerkt, dass die compareTo()
Methode ein zurückgibt int
, kein boolean
. Lassen Sie sich davon nicht überraschen. Wenn wir zwei Objekte vergleichen, gibt es drei Möglichkeiten:
а < b
a > b
a == b
.
boolean
hat nur zwei Werte: wahr und falsch, was zum Vergleichen von Objekten nicht gut funktioniert. Mit int
ist alles viel einfacher. Wenn der Rückgabewert ist > 0
, dann a > b
. Wenn das Ergebnis von compareTo
ist < 0
, dann a < b
. Und wenn das Ergebnis ist == 0
, dann sind zwei Objekte gleich: a == b
. Unserer Klasse beizubringen, Autos nach Baujahr zu sortieren, ist ein Kinderspiel:
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
Aber was ist hier los? Wir nehmen ein Autoobjekt ( this
), ermitteln das Herstellungsjahr dieses Autos und subtrahieren davon das Herstellungsjahr eines anderen Autos (desjenigen, mit dem das Objekt verglichen wird). Wenn das Baujahr des ersten Autos höher ist, gibt die Methode einen zurück int > 0
. Das bedeutet, dass das this car >
Auto o
. Wenn umgekehrt das Baujahr des Zweitwagens ( о
) größer ist, dann gibt die Methode eine negative Zahl zurück, was bedeutet, dass o > this
. Wenn sie schließlich gleich sind, gibt die Methode zurück 0
. Dieser einfache Mechanismus reicht uns bereits aus, um Sammlungen von Car
Objekten zu sortieren! Sie müssen nichts weiter tun. Hör zu:
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);
// There was previously an error here
Collections.sort(cars);
System.out.println(cars);
}
}
Konsolenausgabe:
[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}]
Die Autos sind so sortiert, wie wir es wollen! :) Wann sollte ich verwenden Comparable
? Die in implementierte Vergleichsmethode Comparable
wird als natürliche Reihenfolge bezeichnet. Dies liegt daran, dass Sie in der compareTo()
Methode die gebräuchlichste bzw. natürlichste Art des Vergleichs von Objekten dieser Klasse definieren. Java hat bereits eine natürliche Ordnung. Java weiß beispielsweise, dass Zeichenfolgen am häufigsten alphabetisch und Zahlen nach zunehmendem numerischen Wert sortiert werden. Wenn Sie die Methode daher sort()
für eine Liste von Zahlen oder Zeichenfolgen aufrufen, werden diese sortiert. Wenn unser Programm normalerweise Autos nach Baujahr vergleicht und sortiert, sollten wir die natürliche Sortierung für Autos mithilfe der Comparable<Car>
Schnittstelle und des definierencompareTo()
Methode. Was aber, wenn uns das nicht reicht? Stellen wir uns vor, dass unser Programm nicht so einfach ist. In den meisten Fällen kommt uns die natürliche Sortierung der Autos, die wir nach Baujahren festgelegt haben, entgegen. Aber manchmal sind unsere Kunden Liebhaber des schnellen Fahrens. Wenn wir einen Autokatalog zur Durchsicht vorbereiten, sollten die Autos nach Höchstgeschwindigkeit sortiert sein. Angenommen, wir müssen in 15 % der Fälle so sortieren. Car
Dies reicht eindeutig nicht aus, um die natürliche Sortierung der Klasse auf Geschwindigkeit statt auf Herstellungsjahr festzulegen . Aber wir können 15 % unserer Kunden nicht ignorieren. Also, was machen wir? Hier kommt uns eine weitere Schnittstelle zu Hilfe: Comparator
. Es handelt sich genau wie Comparable
um eine parametrisierte Schnittstelle. Was ist der Unterschied? Comparable
macht unsere Objekte „vergleichbar“ und definiert ihre natürlichste Sortierreihenfolge, also die Sortierreihenfolge, die in den meisten Fällen verwendet wird. Comparator
ist eine separate „Vergleichs“-Schnittstelle. Wenn wir eine spezielle Sortierreihenfolge implementieren müssen, müssen wir nicht in die Car
Klasse gehen und die Logik von ändern compareTo()
. Stattdessen können wir eine separate Klasse erstellen, die Comparator implementiert, und ihr beibringen, wie die von uns benötigte Sortierung durchgeführt wird!
import java.util.Comparator;
public class MaxSpeedCarComparator implements Comparator<Car> {
@Override
public int compare(Car o1, Car o2) {
return o1.getMaxSpeed() - o2.getMaxSpeed();
}
}
Wie Sie sehen, Comparator
ist unser Ansatz ziemlich einfach. Wir müssen nur eine Schnittstellenmethode implementieren: compare()
. Es nimmt zwei Car
Objekte als Eingaben und vergleicht ihre Maximalgeschwindigkeiten auf die übliche Weise (durch Subtraktion). compareTo()
Es gibt wie auch an zurück int
, und das Prinzip des Vergleichs ist dasselbe. Wie nutzen wir das? Es ist alles ganz einfach:
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);
}
}
Konsolenausgabe:
[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}]
Wir erstellen einfach ein Komparatorobjekt und übergeben es Collections.sort()
zusammen mit der zu sortierenden Liste an die Methode. Wenn die sort()
Methode einen Komparator empfängt, verwendet sie nicht die in der Methode Car
der Klasse definierte natürliche Sortierung compareTo()
. Stattdessen wendet es den Sortieralgorithmus an, der durch den ihm übergebenen Komparator definiert wird. Welche Vorteile bietet dies? Erstens die Kompatibilität mit vorhandenem Code. Wir haben eine neue, spezielle Sortiermethode entwickelt und gleichzeitig die bestehende beibehalten, die am häufigsten verwendet wird. Car
Wir haben die Klasse überhaupt nicht berührt . Es war ein Comparable
, und so bleibt es:
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();
}
// ...getters, setters, toString()
}
Zweitens Flexibilität. Wir können beliebig viele Sortieralgorithmen hinzufügen. Wir können Autos beispielsweise nach Farbe, Geschwindigkeit, Gewicht oder danach sortieren, wie oft ein Auto in Batman-Filmen verwendet wurde. Wir müssen lediglich eine zusätzliche erstellen Comparator
. Das ist es! Heute haben Sie zwei sehr wichtige Mechanismen untersucht, die Sie häufig in realen Projekten bei der Arbeit verwenden werden. Aber wie Sie wissen, ist Theorie ohne Praxis nichts. Jetzt ist es an der Zeit, Ihr Wissen zu festigen und einige Aufgaben zu erledigen!
GO TO FULL VERSION