Part 5

Objekte und Referenzen

Lassen Sie uns mit Objekten und Referenzen weiterarbeiten. Nehmen wir an, wir können die Klasse verwenden, die eine Person repräsentiert, wie unten gezeigt. Die Klasse Person hat Objektvariablen wie Name, Alter, Gewicht und Größe. Zusätzlich enthält sie Methoden zur Berechnung des Body-Mass-Index und andere Methoden.

public class Person {

    private String name;
    private int age;
    private int weight;
    private int height;

    public Person(String name) {
        this(name, 0, 0, 0);
    }

    public Person(String name, int age, int height, int weight) {
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.height = height;
    }

    // weitere Konstruktoren und Methoden

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public int getHeight() {
        return this.height;
    }

    public void growOlder() {
        this.age = this.age + 1;
    }

    public void setHeight(int newHeight) {
        this.height = newHeight;
    }

    public void setWeight(int newWeight) {
        this.weight = newWeight;
    }

    public double bodyMassIndex() {
        double heightPerHundred = this.height / 100.0;
        return this.weight / (heightPerHundred * heightPerHundred);
    }

    @Override
    public String toString() {
        return this.name + ", age " + this.age + " years";
    }
}

Was passiert genau, wenn ein neues Objekt erstellt wird?

Person joan = new Person("Joan Ball");

Ein Konstruktoraufruf mit dem Befehl new bewirkt mehrere Dinge. Zuerst wird im Arbeitsspeicher des Computers Platz für die Speicherung der Objektvariablen reserviert. Dann werden Standard- oder Anfangswerte für die Objektvariablen gesetzt (z.B. erhält eine Variable vom Typ int einen Anfangswert von 0). Schließlich wird der Quellcode im Konstruktor ausgeführt.

Ein Konstruktoraufruf gibt eine Referenz auf ein Objekt zurück. Eine Referenz ist eine Information über den Speicherort der Objektdaten.

olio joan

Der Wert der Variablen wird also auf eine Referenz gesetzt, d.h. auf das Wissen über den Speicherort der zugehörigen Objektdaten. Das obige Bild zeigt auch, dass Strings — wie der Name unserer Beispielperson — ebenfalls Objekte sind.

Zuweisung einer Referenztyp-Variablen kopiert die Referenz

Fügen wir eine Person-Typ-Variable namens ball in das Programm ein und weisen joan als Anfangswert zu. Was passiert dann?

Person joan = new Person("Joan Ball");
System.out.println(joan);

Person ball = joan;

Die Anweisung Person ball = joan; erstellt eine neue Person-Variable ball und kopiert den Wert der Variablen joan als ihren Wert. Als Ergebnis verweist ball auf dasselbe Objekt wie joan.

olio joan ja ball

Schauen wir uns das gleiche Beispiel etwas genauer an.

Person joan = new Person("Joan Ball");
System.out.println(joan);

Person ball = joan;
ball.growOlder();
ball.growOlder();

System.out.println(joan);
Beispielausgabe

Joan Ball, age 0 years Joan Ball, age 2 years

Joan Ball — also das Personenobjekt, auf das die Referenz in der Variablen joan zeigt — ist zu Beginn 0 Jahre alt. Danach wird der Wert der Variablen joan der Variablen ball zugewiesen (also kopiert). Das Personenobjekt ball wird um zwei Jahre älter, und Joan Ball altert dementsprechend!

Der interne Zustand eines Objekts wird nicht kopiert, wenn der Wert einer Variablen zugewiesen wird. Es wird kein neues Objekt in der Anweisung Person ball = joan; erstellt — der Wert der Variablen ball wird auf eine Kopie von joans Wert gesetzt, also auf eine Referenz auf ein Objekt.

olio joan ja ball 2

Als nächstes wird das Beispiel so fortgesetzt, dass ein neues Objekt für die Variable joan erstellt und eine Referenz darauf als Wert der Variablen zugewiesen wird. Die Variable ball verweist weiterhin auf das zuvor erstellte Objekt.

Person joan = new Person("Joan Ball");
System.out.println(joan);

Person ball = joan;
ball.growOlder();
ball.growOlder();

System.out.println(joan);

joan = new Person("Joan B.");
System.out.println(joan);

Das folgende wird ausgegeben:

Beispielausgabe

Joan Ball, age 0 years Joan Ball, age 2 years Joan B., age 0 years

Am Anfang enthält die Variable joan eine Referenz auf ein Objekt, aber am Ende wurde eine Referenz auf ein anderes Objekt als ihr Wert kopiert. Hier ist eine Darstellung der Situation nach der letzten Codezeile.

olio joan ja ball 3

null-Wert einer Referenzvariablen

Erweitern wir das Beispiel weiter, indem wir den Wert der Referenzvariablen ball auf null setzen, also auf eine Referenz "auf nichts". Die null-Referenz kann als Wert jeder Referenztyp-Variablen gesetzt werden.

Person joan = new Person("Joan Ball");
System.out.println(joan);

Person ball = joan;
ball.growOlder();
ball.growOlder();

System.out.println(joan);

joan = new Person("Joan B.");
System.out.println(joan);

ball = null;

Die Situation des Programms nach der letzten Zeile ist im folgenden Bild dargestellt.

olio joan ja ball null

Das Objekt, dessen Name Joan Ball ist, wird von niemandem mehr referenziert. Mit anderen Worten, das Objekt ist zu "Müll" geworden. In der Programmiersprache Java muss sich der Programmierende nicht um die Speichernutzung des Programms kümmern. Von Zeit zu Zeit bereinigt der automatische Müllsammler der Java-Sprache die Objekte, die zu Müll geworden sind. Wenn keine Müllsammlung stattfinden würde, würden die Müllobjekte bis zum Ende der Programmausführung einen Speicherplatz belegen.

Sehen wir uns an, was passiert, wenn wir versuchen, eine Variable auszugeben, die auf "nichts" referenziert, also null.

Person joan = new Person("Joan Ball");
System.out.println(joan);

Person ball = joan;
ball.growOlder();
ball.growOlder();

System.out.println(joan);

joan = new Person("Joan B.");
System.out.println(joan);

ball = null;
System.out.println(ball);
Beispielausgabe

Joan Ball, age 0 years Joan Ball, age 2 years Joan B., age 0 years null

Das Ausgeben einer null-Referenz gibt "null" aus. Was passiert, wenn wir versuchen, eine Methode, z.B. growOlder, auf einem Objekt aufzurufen, das auf nichts verweist:

Person joan = new Person("Joan Ball");
System.out.println(joan);

joan = null;
joan.growOlder();

Das Ergebnis:

Beispielausgabe

Joan Ball, age 0 years Exception in thread "main" java.lang.NullPointerException at Main.main(Main.java:(row)) Java Result: 1

Schlimme Dinge passieren. Dies könnte das erste Mal sein, dass Sie den Text NullPointerException sehen. Während des Programmablaufs ist ein Fehler aufgetreten, der darauf hinweist, dass eine Methode auf einer Variablen aufgerufen wurde, die auf nichts verweist.

Wir versprechen Ihnen, dass dies nicht das letzte Mal ist, dass Sie diesen Fehler sehen. Wenn dies geschieht, ist der erste Schritt, nach Variablen zu suchen, deren Wert null sein könnte. Glücklicherweise ist die Fehlermeldung hilfreich: Sie zeigt an, welche Zeile den Fehler verursacht hat. Probieren Sie es selbst aus!

Loading

Objekt als Methodenparameter

Wir haben gesehen, dass sowohl primitive als auch Referenzvariablen als Methodenparameter fungieren können. Da Objekte Referenzvariablen sind, kann jede Art von Objekt als Methodenparameter definiert werden. Schauen wir uns ein praktisches Beispiel an.

Fahrgeschäfte in Vergnügungsparks erlauben nur Personen, die größer als eine bestimmte Höhe sind. Die Grenze ist nicht für alle Attraktionen gleich. Erstellen wir eine Klasse, die ein Fahrgeschäft im Vergnügungspark repräsentiert. Beim Erstellen eines neuen Objekts erhält der Konstruktor als Parameter den Namen der Attraktion und die kleinste Höhe, die den Eintritt zur Attraktion erlaubt.

public class AmusementParkRide {
    private String name;
    private int lowestHeight;

    public AmusementParkRide(String name, int lowestHeight) {
        this.name = name;
        this.lowestHeight = lowestHeight;
    }

    public String toString() {
        return this.name + ", minimum height: " + this.lowestHeight;
    }
}

Schreiben wir dann eine Methode, die verwendet werden kann, um zu prüfen, ob eine Person groß genug ist, um die Attraktion zu betreten. Die Methode gibt true zurück, wenn die Person, die als Parameter übergeben wird, Zugang erhält, und false ansonsten.

Im Folgenden wird angenommen, dass die Klasse Person die Methode public int getHeight() enthält, die die Größe der Person zurückgibt.

public class AmusementParkRide {
    private String name;
    private int lowestHeight;

    public AmusementParkRide(String name, int lowestHeight) {
        this.name = name;
        this.lowestHeight = lowestHeight;
    }

    public boolean allowedToRide(Person person) {
        if (person.getHeight() < this.lowestHeight) {
            return false;
        }

        return true;
    }

    public String toString() {
        return this.name + ", minimum height: " + this.lowestHeight;
    }
}

Die Methode allowedToRide eines AmusementParkRide-Objekts erhält ein Person-Objekt als Parameter. Wie zuvor wird der Wert der Variablen — in diesem Fall eine Referenz — für die Methode zur Verwendung kopiert. Die Methode verarbeitet eine kopierte Referenz und ruft die Methode getHeight der Person auf, die als Parameter übergeben wurde.

Im Folgenden ist ein Beispiel-Hauptprogramm, in dem die Methode des Fahrgeschäfts zweimal aufgerufen wird: zuerst wird das übergebene Parameter-Objekt matt, und dann das Parameter-Objekt jasper:

Person matt = new Person("Matt");
matt.setWeight(86);
matt.setHeight(180);

Person jasper = new Person("Jasper");
jasper.setWeight(34);
jasper.setHeight(132);

AmusementParkRide waterTrack = new AmusementParkRide("Water track", 140);

if (waterTrack.allowedToRide(matt)) {
    System.out.println(matt.getName() + " may enter the ride");
} else {
    System.out.println(matt.getName() + " may not enter the ride");
}

if (waterTrack.allowedToRide(jasper)) {
    System.out.println(jasper.getName() + " may enter the ride");
} else {
    System.out.println(jasper.getName() + " may not enter the ride");
}

System.out.println(waterTrack);

Die Ausgabe des Programms ist:

Beispielausgabe

Matt may enter the ride Jasper may not enter the ride Water track, minimum height: 140

Was, wenn wir wissen wollten, wie viele Personen das Fahrgeschäft genutzt haben?

Fügen wir dem Fahrgeschäft eine Objektvariable hinzu. Sie verfolgt die Anzahl der Personen, denen der Zutritt erlaubt wurde.

public class AmusementParkRide {
    private String name;
    private int lowestHeight;
    private int visitors;

    public AmusementParkRide(String name, int lowestHeight) {
        this.name = name;
        this.lowestHeight = lowestHeight;
        this.visitors = 0;
    }

    public boolean allowedToRide(Person person) {
        if (person.getHeight() < this.lowestHeight) {
            return false;
        }

        this.visitors++;
        return true;
    }

    public String toString() {
        return this.name + ", minimum height: " + this.lowestHeight +
            ", visitors: " + this.visitors;
    }
}

Jetzt verfolgt auch das zuvor verwendete Beispielprogramm die Anzahl der Besucher, die das Fahrgeschäft genutzt haben.

Person matt = new Person("Matt");
matt.setWeight(86);
matt.setHeight(180);

Person jasper = new Person("Jasper");
jasper.setWeight(34);
jasper.setHeight(132);

AmusementParkRide waterTrack = new AmusementParkRide("Water track", 140);

if (waterTrack.allowedToRide(matt)) {
    System.out.println(matt.getName() + " may enter the ride");
} else {
    System.out.println(matt.getName() + " may not enter the ride");
}

if (waterTrack.allowedToRide(jasper)) {
    System.out.println(jasper.getName() + " may enter the ride");
} else {
    System.out.println(jasper.getName() + " may not enter the ride");
}

System.out.println(waterTrack);

Die Ausgabe des Programms ist:

Beispielausgabe

Matt may enter the ride Jasper may not enter the ride Water track, minimum height: 140, visitors: 1

Loading
Loading
Loading

Objekt als Objektvariable

Objekte können Referenzen auf andere Objekte enthalten.

Arbeiten wir weiter mit Personen und fügen dem Person-Klasse einen Geburtstag hinzu. Eine natürliche Art, einen Geburtstag darzustellen, ist die Verwendung einer Date-Klasse. Wir könnten den Klassennamen Date verwenden, aber um Verwechslungen mit der ähnlich benannten vorhandenen Java-Klasse zu vermeiden, werden wir hier SimpleDate verwenden.

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return this.day;
    }

    public int getMonth() {
        return this.month;
    }

    public int getYear() {
        return this.year;
    }

    @Override
    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }
}

Da wir den Geburtstag kennen, ist es nicht notwendig, das Alter einer Person als separate Objektvariable zu speichern. Das Alter der Person kann aus ihrem Geburtstag abgeleitet werden. Nehmen wir an, dass die Klasse Person jetzt die folgenden Variablen hat.

public class Person {
    private String name;
    private SimpleDate birthday;
    private int weight = 0;
    private int length = 0;

// ...

Erstellen wir einen neuen Konstruktor für die Klasse Person, der es ermöglicht, den Geburtstag festzulegen:

public Person(String name, SimpleDate date) {
    this.name = name;
    this.birthday = date;
}

Zusammen mit dem obigen Konstruktor könnten wir der Person einen weiteren Konstruktor geben, bei dem der Geburtstag als Ganzzahlen angegeben wird.

public Person(String name, int day, int month, int year) {
    this.name = name;
    this.birthday = new SimpleDate(day, month, year);
}

Der Konstruktor erhält als Parameter die verschiedenen Teile des Datums (Tag, Monat, Jahr). Diese werden verwendet, um ein Datumsobjekt zu erstellen, und schließlich wird die Referenz auf dieses Datum als Wert der Objektvariable birthday kopiert.

Ändern wir die toString-Methode der Klasse Person so, dass sie anstelle des Alters den Geburtstag zurückgibt:

public String toString() {
    return this.name + ", born on " + this.birthday;
}

Sehen wir uns an, wie die aktualisierte Klasse Person funktioniert.

SimpleDate date = new SimpleDate(1, 1, 780);
Person muhammad = new Person("Muhammad ibn Musa al-Khwarizmi", date);
Person pascal = new Person("Blaise Pascal", 19, 6, 1623);

System.out.println(muhammad);
System.out.println(pascal);
Beispielausgabe

Muhammad ibn Musa al-Khwarizmi, born on 1.1.780 Blaise Pascal, born on 19.6.1623

Jetzt hat ein Personenobjekt die Objektvariablen name und birthday. Die Variable name ist ein String, der selbst ein Objekt ist; die Variable birthday ist ein SimpleDate-Objekt.

Beide Variablen enthalten eine Referenz auf ein Objekt. Daher enthält ein Personenobjekt zwei Referenzen. Im Bild unten werden Gewicht und Größe überhaupt nicht berücksichtigt.

muhammad ja pascal

Das Hauptprogramm ist also über Stränge mit zwei Person-Objekten verbunden. Eine Person hat einen Namen und einen Geburtstag. Da beide Variablen Objekte sind, existieren diese Attribute am anderen Ende der Stränge.

Der Geburtstag scheint eine gute Erweiterung der Person-Klasse zu sein. Früher haben wir festgestellt, dass die Objektvariable age mit dem Geburtstag berechnet werden kann, also wurde sie entfernt.

Objekt des gleichen Typs als Methodenparameter

Wir arbeiten weiter mit der Klasse Person. Wir erinnern uns, dass Personen ihre Geburtstage kennen:

public class Person {

    private String name;
    private SimpleDate birthday;
    private int height;
    private int weight;

    // ...
}

Wir möchten das Alter von zwei Personen vergleichen. Der Vergleich kann auf verschiedene Weise erfolgen. Wir könnten zum Beispiel eine Methode namens public int ageAsYears() für die Klasse Person implementieren; in diesem Fall würde der Vergleich wie folgt ablaufen:

Person muhammad = new Person("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Person pascal = new Person("Blaise Pascal", 19, 6, 1623);

if (muhammad.ageAsYears() > pascal.ageAsYears()) {
    System.out.println(muhammad.getName() + " is older than " + pascal.getName());
}

Wir werden nun eine „objektorientiertere“ Möglichkeit kennenlernen, das Alter von Personen zu vergleichen.

Wir werden eine neue Methode boolean olderThan(Person compared) für die Klasse Person erstellen. Sie kann verwendet werden, um ein bestimmtes Personenobjekt mit der als Parameter übergebenen Person anhand ihres Alters zu vergleichen.

Die Methode ist wie folgt zu verwenden:

Person muhammad = new Person("Muhammad ibn Musa al-Khwarizmi", 1, 1, 780);
Person pascal = new Person("Blaise Pascal", 19, 6, 1623);

if (muhammad.olderThan(pascal)) {  //  entspricht muhammad.olderThan(pascal)==true
    System.out.println(muhammad.getName() + " is older than " + pascal.getName());
} else {
    System.out.println(muhammad.getName() + " is not older than " + pascal.getName());
}

Das obige Programm fragt, ob al-Khwarizmi älter ist als Pascal. Die Methode olderThan gibt true zurück, wenn das Objekt, das verwendet wird, um die Methode aufzurufen (object.olderThan(objectGivenAsParameter)), älter ist als das Objekt, das als Parameter übergeben wird, und false andernfalls.

In der Praxis rufen wir die Methode olderThan des Objekts auf, das "Muhammad ibn Musa al-Khwarizmi" entspricht, auf das durch die Variable muhammad verwiesen wird. Die Referenz pascal, die dem Objekt "Blaise Pascal" entspricht, wird als Parameter an diese Methode übergeben.

Das Programm druckt:

Beispielausgabe

Muhammad ibn Musa al-Khwarizmi is older than Blaise Pascal

Die Methode olderThan erhält ein Personenobjekt als Parameter. Genauer gesagt, erhält die als Methodenparameter definierte Variable eine Kopie des Werts der übergebenen Variablen. Dieser Wert ist in diesem Fall eine Referenz auf ein Objekt.

Die Implementierung der Methode ist unten illustriert. Beachten Sie, dass die Methode an mehr als einer Stelle einen Wert zurückgeben kann — hier wurde der Vergleich in mehrere Teile aufgeteilt, basierend auf den Jahren, den Monaten und den Tagen:

public class Person {
    // ...

    public boolean olderThan(Person compared) {
        // 1. Vergleichen Sie zuerst die Jahre
        int ownYear = this.getBirthday().getYear();
        int comparedYear = compared.getBirthday().getYear();

        if (ownYear < comparedYear) {
            return true;
        }

        if (ownYear > comparedYear) {
            return false;
        }

        // 2. Dasselbe Geburtsjahr, vergleichen Sie die Monate
        int ownMonth = this.getBirthday().getMonth();
        int comparedMonth = compared.getBirthday().getMonth();

        if (ownMonth < comparedMonth) {
            return true;
        }

        if (ownMonth > comparedMonth) {
            return false;
        }

        // 3. Dasselbe Geburtsjahr und -monat, vergleichen Sie die Tage
        int ownDay = this.getBirthday().getDay();
        int comparedDay = compared.getBirthday().getDay();

        if (ownDay < comparedDay) {
            return true;
        }

        return false;
    }
}

Pausieren wir einen Moment, um über Abstraktion, eines der Prinzipien der objektorientierten Programmierung, nachzudenken. Die Idee hinter der Abstraktion besteht darin, den Programmcode so zu konzeptionieren, dass jedes Konzept seine eigenen klaren Verantwortlichkeiten hat. Wenn wir uns die obige Lösung ansehen, stellen wir jedoch fest, dass die Vergleichsfunktionalität besser in der Klasse SimpleDate als in der Klasse Person platziert wäre.

Wir erstellen eine Methode namens public boolean before(SimpleDate compared) für die Klasse SimpleDate. Die Methode gibt den Wert true zurück, wenn das als Parameter übergebene Datum nach (oder am gleichen Tag wie) dem Datum des Objekts, dessen Methode aufgerufen wird, liegt.

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }

    // wird verwendet, um zu überprüfen, ob dieses Datumsobjekt (`this`)
    // vor dem Datumsobjekt liegt, das als Parameter (`compared`) übergeben wird.
    public boolean before(SimpleDate compared) {
        // zuerst die Jahre vergleichen
        if (this.year < compared.year) {
            return true;
        }

        if (this.year > compared.year) {
            return false;
        }

        // Jahre sind gleich, vergleichen Sie die Monate
        if (this.month < compared.month) {
            return true;
        }

        if (this.month > compared.month) {
            return false;
        }

        // Jahre und Monate sind gleich, vergleichen Sie die Tage
        if (this.day < compared.day) {
            return true;
        }

        return false;
    }
}

Obwohl die Objektvariablen year, month und day gekapselt (private) sind, können wir ihre Werte durch Schreiben von compared.*variableName* lesen. Dies liegt daran, dass auf eine private-Variable von allen Methoden zugegriffen werden kann, die in dieser Klasse enthalten sind. Beachten Sie, dass die Syntax hier dem Aufruf einer Objektmethode entspricht. Anders als beim Aufruf einer Methode beziehen wir uns jedoch auf ein Feld eines Objekts, sodass die Klammern, die einen Methodenaufruf anzeigen, nicht geschrieben werden.

Ein Beispiel, wie die Methode verwendet wird:

public static void main(String[] args) {
    SimpleDate d1 = new SimpleDate(14, 2, 2011);
    SimpleDate d2 = new SimpleDate(21, 2, 2011);
    SimpleDate d3 = new SimpleDate(1, 3, 2011);
    SimpleDate d4 = new SimpleDate(31, 12, 2010);

    System.out.println(d1 + " is earlier than " + d2 + ": " + d1.before(d2));
    System.out.println(d2 + " is earlier than " + d1 + ": " + d2.before(d1));

    System.out.println(d2 + " is earlier than " + d3 + ": " + d2.before(d3));
    System.out.println(d3 + " is earlier than " + d2 + ": " + d3.before(d2));

    System.out.println(d4 + " is earlier than " + d1 + ": " + d4.before(d1));
    System.out.println(d1 + " is earlier than " + d4 + ": " + d1.before(d4));
}
Beispielausgabe

14.2.2011 is earlier than 21.2.2011: true 21.2.2011 is earlier than 14.2.2011: false 21.2.2011 is earlier than 1.3.2011: true 1.3.2011 is earlier than 21.2.2011: false 31.12.2010 is earlier than 14.2.2011: true 14.2.2011 is earlier than 31.12.2010: false

Lassen Sie uns die Methode olderThan der Klasse Person so anpassen, dass wir von nun an die Vergleichsfunktionalität nutzen, die Datumsobjekte bieten.

public class Person {
    // ...

    public boolean olderThan(Person compared) {
        if (this.birthday.before(compared.getBirthday())) {
            return true;
        }

        return false;

        // oder direkter zurückgeben:
        // return this.birthday.before(compared.getBirthday());
    }
}

Jetzt ist der konkrete Vergleich von Daten in der Klasse implementiert, zu der er logisch (basierend auf den Klassennamen) gehört.

Loading

Vergleich der Gleichheit von Objekten (equals)

Beim Arbeiten mit Strings haben wir gelernt, dass Strings mit der Methode equals verglichen werden müssen. So wird es gemacht.

Scanner scanner = new Scanner(System.in);

System.out.println("Enter two words, each on its own line.")
String first = scanner.nextLine();
String second = scanner.nextLine();

if (first.equals(second)) {
    System.out.println("The words were the same.");
} else {
    System.out.println("The words were not the same.");
}

Bei primitiven Variablen wie int kann der Vergleich zweier Variablen mit zwei Gleichheitszeichen erfolgen. Dies liegt daran, dass der Wert einer primitiven Variablen direkt im „Kasten der Variablen“ gespeichert wird. Der Wert von Referenzvariablen ist im Gegensatz dazu eine Adresse des referenzierten Objekts; der „Kasten“ enthält also eine Referenz auf den Speicherort. Die Verwendung von zwei Gleichheitszeichen vergleicht die Gleichheit der in den „Kästen der Variablen“ gespeicherten Werte — bei Referenzvariablen würden solche Vergleiche die Gleichheit der Speicherreferenzen untersuchen.

Die Methode equals ähnelt der Methode toString insofern, als sie verwendet werden kann, auch wenn sie nicht in der Klasse definiert wurde. Die Standardimplementierung dieser Methode vergleicht die Gleichheit der Referenzen. Lassen Sie uns dies mit Hilfe der zuvor geschriebenen SimpleDate-Klasse beobachten.

SimpleDate first = new SimpleDate(1, 1, 2000);
SimpleDate second = new SimpleDate(1, 1, 2000);
SimpleDate third = new SimpleDate(12, 12, 2012);
SimpleDate fourth = first;

if (first.equals(first)) {
    System.out.println("Variables first and first are equal");
} else {
    System.out.println("Variables first and first are not equal");
}

if (first.equals(second)) {
    System.out.println("Variables first and second are equal");
} else {
    System.out.println("Variables first and second are not equal");
}

if (first.equals(third)) {
    System.out.println("Variables first and third are equal");
} else {
    System.out.println("Variables first and third are not equal");
}

if (first.equals(fourth)) {
    System.out.println("Variables first and fourth are equal");
} else {
    System.out.println("Variables first and fourth are not equal");
}
Beispielausgabe

Variables first and first are equal Variables first and second are not equal Variables first and third are not equal Variables first and fourth are equal

Es gibt ein Problem mit dem obigen Programm. Obwohl zwei Daten (first und second) genau dieselben Werte für Objektvariablen haben, sind sie aus der Sicht der Standardmethode equals unterschiedlich.

Wenn wir in der Lage sein möchten, zwei Objekte unseres eigenen Designs mit der Methode equals zu vergleichen, muss diese Methode in der Klasse definiert werden. Die Methode equals wird so definiert, dass sie ein boolescher Wert zurückgibt — der Rückgabewert zeigt an, ob die Objekte gleich sind.

Die Methode equals wird so implementiert, dass sie verwendet werden kann, um das aktuelle Objekt mit jedem anderen Objekt zu vergleichen. Die Methode erhält ein Objekt vom Typ Object als einzigen Parameter — alle Objekte sind vom Typ Object, zusätzlich zu ihrem eigenen Typ. Die Methode equals vergleicht zunächst, ob die Adressen gleich sind: Wenn ja, sind die Objekte gleich. Danach wird überprüft, ob die Typen der Objekte gleich sind: Wenn nicht, sind die Objekte nicht gleich. Als Nächstes wird das Objekt vom Typ Object, das als Parameter übergeben wurde, mit einem Typecast in den Typ des untersuchten Objekts konvertiert, sodass die Werte der Objektvariablen verglichen werden können. Unten ist der Gleichheitsvergleich für die Klasse SimpleDate implementiert.

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return this.day;
    }

    public int getMonth() {
        return this.month;
    }

    public int getYear() {
        return this.year;
    }

    public boolean equals(Object compared) {
        // wenn die Variablen an derselben Position liegen, sind sie gleich
        if (this == compared) {
            return true;
        }

        // wenn der Typ des verglichenen Objekts nicht `SimpleDate` ist, sind die Objekte nicht gleich
        if (!(compared instanceof SimpleDate)) {
            return false;
        }

        // Konvertieren Sie das Objekt `Object` in ein `SimpleDate`-Objekt namens `comparedSimpleDate`.
        SimpleDate comparedSimpleDate = (SimpleDate) compared;

        // wenn die Werte der Objektvariablen gleich sind, sind die Objekte gleich
        if (this.day == comparedSimpleDate.day &&
            this.month == comparedSimpleDate.month &&
            this.year == comparedSimpleDate.year) {
            return true;
        }

        // andernfalls sind die Objekte nicht gleich
        return false;
    }


    @Override
    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }
}

Vergleichsfunktionalität für Person-Objekte

Eine ähnliche Vergleichsfunktionalität kann auch für Person-Objekte implementiert werden. Im Folgenden wurde der Vergleich für Person-Objekte implementiert, die kein separates SimpleDate-Objekt besitzen. Beachten Sie, dass die Namen von Personen Strings (also Objekte) sind, weshalb die equals-Methode zum Vergleich verwendet wird.

public class Person {

    private String name;
    private int age;
    private int weight;
    private int height;

    // Konstruktoren und Methoden


    public boolean equals(Object compared) {
        // Wenn die Variablen sich an derselben Speicherposition befinden, sind sie gleich
        if (this == compared) {
            return true;
        }

        // Wenn das verglichene Objekt nicht vom Typ Person ist, sind die Objekte nicht gleich
        if (!(compared instanceof Person)) {
            return false;
        }

        // Konvertiere das Objekt in ein Person-Objekt
        Person comparedPerson = (Person) compared;

        // Wenn die Werte der Objektvariablen gleich sind, sind die Objekte gleich
        if (this.name.equals(comparedPerson.name) &&
            this.age == comparedPerson.age &&
            this.weight == comparedPerson.weight &&
            this.height == comparedPerson.height) {
            return true;
        }

        // Andernfalls sind die Objekte nicht gleich
        return false;
    }

    // .. Methoden
}
Loading
Loading

Objektgleichheit und Listen

Schauen wir uns an, wie die Methode equals mit Listen verwendet wird. Angenommen, wir haben die zuvor beschriebene Klasse Bird ohne equals-Methode.

public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }
}

Lassen Sie uns eine Liste erstellen und einen Vogel hinzufügen. Danach überprüfen wir, ob dieser Vogel in der Liste enthalten ist.

ArrayList<Bird> birds = new ArrayList<>()
Bird red = new Bird("Red");

if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}

birds.add(red);
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}


System.out.println("However!");

red = new Bird("Red");
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}
Beispielausgabe

Red is not on the list. Red is on the list. However! Red is not on the list.

Wir können im obigen Beispiel feststellen, dass wir in einer Liste nach unseren eigenen Objekten suchen können. Zuerst, als der Vogel noch nicht zur Liste hinzugefügt wurde, wird er nicht gefunden — und nach dem Hinzufügen wird er gefunden. Wenn das Programm das red-Objekt durch ein neues Objekt ersetzt, das genau denselben Inhalt wie zuvor hat, ist es nicht mehr gleich dem Objekt auf der Liste und kann daher nicht in der Liste gefunden werden.

Die Methode contains einer Liste verwendet die equals-Methode, die für die Objekte definiert ist, bei der Suche nach Objekten. Im obigen Beispiel hat die Klasse Bird keine Definition für diese Methode, daher kann ein Vogel mit genau demselben Inhalt — aber einer anderen Referenz — nicht in der Liste gefunden werden.

Lassen Sie uns die equals-Methode für die Klasse Bird implementieren. Die Methode prüft, ob die Namen der Objekte gleich sind — wenn die Namen übereinstimmen, gelten die Vögel als gleich.

public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }

    public boolean equals(Object compared) {
        // Wenn die Variablen sich an derselben Speicherposition befinden, sind sie gleich
        if (this == compared) {
            return true;
        }

        // Wenn das verglichene Objekt nicht vom Typ Bird ist, sind die Objekte nicht gleich
        if (!(compared instanceof Bird)) {
            return false;
        }

        // Konvertiere das Objekt in ein Bird-Objekt
        Bird comparedBird = (Bird) compared;

        // Wenn die Werte der Objektvariablen gleich sind, sind die Objekte ebenfalls gleich
        return this.name.equals(comparedBird.name);

        /*
        // Der Vergleich der Namen oben entspricht dem folgenden Code

        if (this.name.equals(comparedBird.name)) {
            return true;
        }

        // Andernfalls sind die Objekte nicht gleich
        return false;
        */
    }
}

Jetzt erkennt die contains-Methode der Liste Vögel mit identischem Inhalt.

ArrayList<Bird> birds = new ArrayList<>()
Bird red = new Bird("Red");

if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}

birds.add(red);
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}


System.out.println("However!");

red = new Bird("Red");
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}
Beispielausgabe

Red is not on the list. Red is on the list. However! Red is on the list.

Loading
Loading

Objekt als Rückgabewert einer Methode

Wir haben gesehen, dass Methoden boolesche Werte, Zahlen und Strings zurückgeben können. Es ist leicht zu erraten, dass eine Methode ein Objekt eines beliebigen Typs zurückgeben kann.

Im nächsten Beispiel präsentieren wir einen einfachen Zähler, der die Methode clone besitzt. Diese Methode kann verwendet werden, um eine Kopie des Zählers zu erstellen, also ein neues Zählerobjekt, das denselben Wert zum Zeitpunkt seiner Erstellung hat wie der zu klonende Zähler.

public class Counter {
    private int value;

    // Beispiel für die Verwendung mehrerer Konstruktoren:
    // Sie können einen anderen Konstruktor von einem Konstruktor aus aufrufen, indem Sie this verwenden
    // Beachten Sie, dass der this-Aufruf an der ersten Zeile des Konstruktors stehen muss
    public Counter() {
        this(0);
    }

    public Counter(int initialValue) {
        this.value = initialValue;
    }

    public void increase() {
        this.value = this.value + 1;
    }

    public String toString() {
        return "value: " + value;
    }

    public Counter clone() {
        // Erstellen Sie ein neues Zählerobjekt, das den Wert des geklonten Zählers als Anfangswert erhält
        Counter clone = new Counter(this.value);

        // Geben Sie die Kopie an den Aufrufer zurück
        return clone;
    }
}

Ein Beispiel zur Verwendung von Zählern folgt:

Counter counter = new Counter();
counter.increase();
counter.increase();

System.out.println(counter);         // gibt 2 aus

Counter clone = counter.clone();

System.out.println(counter);         // gibt 2 aus
System.out.println(clone);           // gibt 2 aus

counter.increase();
counter.increase();
counter.increase();
counter.increase();

System.out.println(counter);         // gibt 6 aus
System.out.println(clone);           // gibt 2 aus

clone.increase();

System.out.println(counter);         // gibt 6 aus
System.out.println(clone);           // gibt 3 aus

Unmittelbar nach dem Klonvorgang sind die in der Kopie und im geklonten Objekt enthaltenen Werte identisch. Sie sind jedoch zwei verschiedene Objekte, sodass das Erhöhen des Werts eines Zählers den Wert des anderen in keiner Weise beeinflusst.

Ebenso könnte ein Factory-Objekt verwendet werden, um neue Car-Objekte zu erstellen und zurückzugeben. Im Folgenden finden Sie eine Skizze des Fabrikentwurfs – die Fabrik kennt auch die Marke der hergestellten Autos.

public class Factory {
    private String make;

    public Factory(String make) {
        this.make = make;
    }

    public Car procuceCar() {
        return new Car(this.make);
    }
}
Loading
Loading
Sie haben das Ende dieses Abschnitts erreicht! Weiter zum nächsten Abschnitt: