So schreiben Sie equal() in Java

Feb 22 2023
Am Ende dieses Artikels werden alle erforderlichen Informationen behandelt, um die beste Methode equal() in der Programmiersprache Java schreiben zu können. Die Einzelheiten der Gleichheit werden anhand einer mathematischen Definition und in der Art der Programmiersprache Java behandelt, um die grundlegenden Informationen abzudecken, die für die Verwendung der Best Practice der Methode equal() erforderlich sind.

Am Ende dieses Artikels werden alle erforderlichen Informationen behandelt, um die beste Methode equal() in der Programmiersprache Java schreiben zu können. Die Einzelheiten der Gleichheit werden anhand einer mathematischen Definition und in der Art der Programmiersprache Java behandelt, um die grundlegenden Informationen abzudecken, die für die Verwendung der Best Practice der Methode equal() erforderlich sind .

Mathematische Definition von Gleichheit

Laut Wikipedia lautet die Definition der mathematischen Gleichheit

In der Mathematik ist Gleichheit eine Beziehung zwischen zwei Größen oder allgemeiner zwei mathematischen Ausdrücken, die besagt, dass die Größen denselben Wert haben oder dass die Ausdrücke dasselbe mathematische Objekt darstellen. Die Gleichheit zwischen A und B wird als A = B geschrieben und als A gleich B ausgesprochen .

Definition von Gleichheit in Java

In der Programmiersprache Java bedeutet Gleichheit einfach, dass man den gleichen Wert hat. Es bedeutet manchmal wörtlich, dass es den gleichen Wert hat, und manchmal soll es die gleiche Struktur in Bezug auf die Eigenschaften des Objekts haben.

Wenn die Gleichheit zweier primitiver Typwerte überprüft werden muss, wird das Gleichheitszeichen direkt verwendet, wie im folgenden Beispiel;

int a = 5;
int b = 6;
int c = 5;

// Prints "false" since a and b do not have the same value.
System.out.println(a == b);

// Prints "true" since a and c have the same value.
System.out.println(a == c);

Andererseits ist es manchmal erforderlich, die Gleichheit zweier Objekte zu überprüfen. In Bezug auf Objekte gibt es zwei unterschiedliche Ansätze, die in unterschiedlichen Phasen des Prozesses zur Überprüfung der Gleichheit zweier Objekte verwendet werden. Kommen wir zu den Details.

Referenzielle Gleichheit

In der Programmiersprache Java werden die tatsächlichen Werte der Objekte irgendwo im Speicher gespeichert und durch die Objektreferenzen, die ebenfalls irgendwo im Speicher gespeichert sind, auf sie verwiesen. Schauen wir uns den folgenden Codeausschnitt an.

// Create an object with "new" operator.
// Point to object with object reference "a".
Object a = new Object();

// Create another object reference.
// Point to the same object as the reference "a" is pointing.
Object b = a;

// Output is true.
System.out.println(a == b);

// Create another object with "new" operator.
// Point to object with object reference "b".
b = new Object();

// Output is false.
System.out.println(a == b);

In einem solchen Szenario sind „a“ und „b“ tatsächlich zwei Referenzen, die auf dasselbe Objekt verweisen, sodass man direkt sagen kann, dass „a“ gleich „b“ ist, da die Referenzgleichheit erhalten wird.

Das umgekehrte Szenario gilt jedoch nicht immer. Zwei unterschiedliche Referenzen können auf unterschiedliche Speicherorte verweisen, aber die Objekte, auf die die Referenzen verweisen, können dennoch gleich sein.

Im normalen Leben werden manchmal Mengen verglichen. Wenn Sie beispielsweise zwei Äpfel haben und Ihr Freund zwei Äpfel, dann ist die Anzahl der Äpfel, die Sie und Ihr Freund haben, gleich.

Andererseits können Dinge manchmal auch hinsichtlich ihrer Eigenschaften oder Struktur verglichen werden. Kommen wir zu den Details dieses Szenarios.

Strukturelle Gleichheit

Strukturelle Gleichheit ist die Art, mit der Objekte anhand ihrer Eigenschaften verglichen werden. Um diese Art von Gleichheit zu überprüfen, ist ein ausgefeilteres Tool erforderlich und nicht nur der „==“-Operator. Es handelt sich um die Methode equal(). Es stammt von der Object-Klasse, die sich an der höchsten Stelle der Klassenhierarchie in der Programmiersprache Java befindet. Schauen wir uns das kommende Java-Code-Snippet an.

public class Car {

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;
    
}

Car car1 = new Car("Audi","R8", 2023);
Car car2 = new Car("Audi","R8", 2023);
Car car3 = new Car("Audi","R8", 2021);

System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));

public boolean equals(Object obj) {
    return (this == obj);
}

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
    
}

In der zweiten Zeile beginnen wir mit der Überprüfung der Identität der Objekte. Wenn die Objektreferenz o null ist oder beide Objektreferenzen auf Objekte verweisen, die unterschiedlichen Klassen angehören, kann direkt gesagt werden, dass diese beiden Objekte nicht die gleiche Identität haben und nicht gleich sind. Sollte dies nicht der Fall sein, können wir endlich die tatsächliche Struktur der Objekte überprüfen.

In der dritten Zeile der Methode wandeln wir die Referenz o in den entsprechenden Klassentyp um, da die Methode von der Object-Klasse überschrieben wird und wir jetzt wissen, dass sie zur Car-Klasse gehört.

Schließlich wird in der letzten Zeile der Methode die Struktur zweier Objekte überprüft und dies ist die eigentliche Zeile, in der die Identität definiert wird. Wenn Marken, Modelle und Modelljahre gleich sind, sind diese beiden Objekte einfach gleich. Mit anderen Worten: Dies ist die strukturelle Gleichheit.

Wir haben die Eigenschaften der mathematischen Gleichheit in der Grundschule gelernt. Einige dieser Eigenschaften müssen auch für die Methode equal() gültig sein . Das bedeutet, dass diese Eigenschaften zu den Bestandteilen der Methode equal() hinzugefügt werden müssen . Sehen wir uns diese Eigenschaften im Hinblick auf die mathematische Gleichheit und im Hinblick auf die Gleichheit in Java an.

Reflexive Eigenschaft

In der mathematischen Definition der reflexiven Eigenschaft gilt für alle reellen Zahlen x ;

x = x

Eine Zahl ist sich selbst gleich.

Im Sinne der Java-Gleichheit muss ein Objekt sich selbst entsprechen;

Car car1 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car1));

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return mileage < car.mileage && brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }

Symmetrische Eigenschaft

Im Hinblick auf die mathematische Definition der symmetrischen Eigenschaft für alle reellen Zahlen x und y ;

Wenn x = y , dann ist y = x

Die Reihenfolge der Gleichheit spielt keine Rolle.

In Bezug auf die Java-Gleichheit gilt: Wenn Objekt a gleich Objekt b ist, muss Objekt b gleich Objekt a sein.

Car car1 = new Car("Audi","R8",2013);
Car car2 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Car)) return false;
        Car car = (Car) o;
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
}

public class SportCar extends Car {
    
    private String engineType;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof SportCar)) return false;
        SportCar sportCar = (SportCar) o;
        return engineType.equals(sportCar.engineType);
    }
}

Car car1 = new Car("Audi", "R8", 2012, 1000);
SportCar car2 = new SportCar("Audi", "R8", 2012, 1000, "4.0");
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

Transitive Eigenschaft

In der mathematischen Definition der transitiven Eigenschaft gilt für alle reellen Zahlen x , y und z ;

wenn x = y und y = z , dann ist x = z

Zwei Zahlen gleicher Zahl sind einander gleich.

In Bezug auf die Java-Gleichheit gilt: Wenn Objekt a gleich Objekt b und Objekt b gleich Objekt c ist, dann muss Objekt a gleich Objekt c sein;

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

public class SportCar extends Car {
    
    private Turbo turbo;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Car)) return false;
        if (!(o instanceof SportCar)) return o.equals(this);
        return super.equals(o) && ((SportCar) o).turbo == turbo;
    }
}

public class Turbo {
    private String power;

    public Turbo(String power) {
        this.power = power;
    }
}

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

Konsistente Eigenschaft

Dies ist eine andere Eigenschaft, da es mathematisch kein exaktes Äquivalent gibt. In diesem Fall müssen wir bei aufeinanderfolgenden Ausführungen der Methode equal() das gleiche Ergebnis erhalten.

Schauen wir uns den folgenden Codeausschnitt an.

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = new Car("Audi", "R8", 2012, 1000);

for (int i = 0; i < 100000; i++) {
    System.out.println(car1.equals(car2));
}

@Override
public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Car)) return false;
    Car car = (Car) o;
    Random random = new Random();
    return brand.equals(car.brand) && model.equals(car.model) && (random.nextInt(10) + 2013) == car.modelYear;
}

Hinweis: Dieses Beispiel befriedigt mich nicht wirklich. Nach ausführlicher Recherche werde ich es durch ein besseres ersetzen. In der Java-Dokumentation der Methode equal() heißt es eindeutig, dass „mehrere Aufrufe von „ x.equals(y)consistent return“ trueoder „consistent return false“ erfolgen. Für diese Aussage muss ein stichhaltiges Beispiel für einen Verstoß vorliegen.

Null-Parametereigenschaft

So nenne ich diese Immobilie.

Bitte schauen Sie sich den folgenden Codeausschnitt an.

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = null;
System.out.println(car1.equals(car2));

Nun ist klar, welche Eigenschaften eine Equals()-Methodenimplementierung haben sollte, um eine korrekte Berechnung der Gleichheit zu ermöglichen. Außerdem haben wir die möglichen Verletzungsszenarien gesehen und können sie nun aus Gründen der Codequalität und der Geschäftslogik vermeiden.

Heutzutage bieten viele IDEs Generierungsmechanismen, um die bestmöglichen Implementierungen der equal()- Methode zu generieren. Es ist eine gute Idee, die meiste Zeit bei ihnen zu bleiben, anstatt die Methode equal() zu implementieren . Sehen wir uns ein Beispiel von Intellij IDEA an .

@Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Car car = (Car) o;
       return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear) && mileage.equals(car.mileage);
   }

In der ersten Zeile wird die referenzielle Gleichheit geprüft, da zwei Objektreferenzen auf dasselbe Objekt verweisen können.

In der zweiten Zeile wird die Validierung der Nullparametereigenschaft sichergestellt und die gleiche if-Bedingung fungiert auch als Wächter-if-Klausel , indem sie den Klassentyp der Objekte steuert, auf die durch Referenzen verwiesen wird, um eine Verletzung der transitiven Eigenschaft und der symmetrischen Eigenschaft zu verhindern .

In der dritten Zeile wird die Objektreferenz in den entsprechenden Klassentyp umgewandelt, in unserem Beispiel Car, um die Eigenschaften des Car-Objekts zu erreichen.

Abschließend wird in der vierten Zeile die Gleichheit hinsichtlich der Strukturgleichheit überprüft.

Es gibt andere Vorlagen für die Generierung der Methode equal() . Sie können sie auch überprüfen.

Wenn Sie die Implementierung selbst schreiben möchten, beachten Sie bitte, dass die Implementierung die Gültigkeit aller Methodeneigenschaften von equal() sicherstellen muss.

Am Ende des Artikels gibt es noch eine sehr wichtige Lücke, die geschlossen werden muss. Öffnen Sie Ihre IDE und versuchen Sie, die Methode equal() zu generieren . Sie werden sehen, dass es keine Option gibt, nur die Methode equal() ohne die Methode hashCode() zu generieren . Aufgrund einiger Sammlungstypen in der Programmiersprache Java empfiehlt es sich, diese beiden Methoden nicht voneinander zu trennen. In der offiziellen Java-Dokumentation heißt es außerdem, dass es im Allgemeinen erforderlich ist, die Methode hashCode() immer dann zu überschreiben, wenn die Methode equal() überschrieben wird. Ich möchte entsprechende Informationen zum Hashing und zur hashCode()- Methode in einem anderen Artikel von mir geben.

In einigen Ausnahmefällen wie Entitätsklassen von JPA müssen wir uns manchmal auch nicht an die Eigenschaften der Methode equal() halten. Es verdient auch einen eigenen Artikel.

Abschließend

In diesem Artikel werden alle wichtigen Punkte zur Implementierung der korrekten Methode equal() behandelt. Ich empfehle Ihnen außerdem, die entsprechenden Komponententests zu schreiben, um die Validierung der Eigenschaften der Methode equal() zu testen . Vergessen Sie nicht, einen Blick auf die anderen Ressourcen zu werfen, die im Internet verfügbar sind.

Erstens scheint es ein sehr einfaches und primitives Thema zu sein, da es sich um die Grundlagen des objektorientierten Designs handelt. Wie aus den Details hervorgeht, ist es jedoch sehr wichtig, diese Grundkonzepte nicht zu überspringen und sie vollständig zu verstehen.