Objektreferenzen in Java: Werte, Aliasse und Gleichheit

Lerne, was eine Java-Variable tatsächlich enthält: einen primitiven Wert oder eine Referenz auf ein Objekt. Verfolge Aliasse und null, übergib Objekte an Methoden und gib sie zurück. Baue Klassen aus anderen Objekten und aus Objektlisten auf. Schreibe anschließend equals und hashCode, damit Listen, Mengen und Maps deine Objekte anhand ihres Inhalts vergleichen. Trenne außerdem die Textoberfläche eines Programms von seiner Logik.

AnfängerEinheit 6 von 11 in Kurs: Java-Programmierung – Klassen, Vererbung, Collections und Streams

Was du lernst

  1. Erkläre, was eine Java-Variable bei primitiven Datentypen und Referenztypen enthält, sage die Wirkung einer Zuweisung für beide voraus und gehe mit Variablen um, die auf kein Objekt verweisen.
    • Vergleiche Variablen primitiver Typen mit Referenzvariablen
      Nenne die primitiven Datentypen von Java, erkläre, dass eine Referenzvariable eine Referenz auf ein Objekt enthält, und sage voraus, was beim Kopieren beider Variablenarten geschieht.
    • Verfolge Aliasse, Neuzuweisungen und unerreichbare Objekte
      Verfolge mehrere Variablen, die auf ein Objekt verweisen, unterscheide eine Objektänderung von einer Neuzuweisung und erkläre, was mit Objekten geschieht, auf die nichts mehr verweist.
    • Gehe sicher mit der Nullreferenz um
      Erkläre, was null bedeutet, erkenne, wo es vorkommt, verhindere eine NullPointerException und entscheide, wann null ein sinnvoller Wert ist.
  2. Erkläre, wie Java Argumente übergibt. Schreibe Methoden, die Objekte entgegennehmen und ändern oder mit einem anderen Objekt derselben Klasse vergleichen, und gib neue oder vorhandene Objekte sicher zurück.
    • Verwende Objekte als Methodenparameter
      Sage voraus, was eine Methode ändern kann und was nicht, wenn sie einen primitiven Wert oder eine Referenz erhält. Schreibe Methoden, die Objekte einer anderen oder der eigenen Klasse entgegennehmen.
    • Gib Objekte aus Methoden zurück
      Schreibe Methoden, die neue Objekte erzeugen und zurückgeben oder Referenzen auf bereits vorhandene Objekte zurückgeben. Vermeide es, den internen Zustand eines Objekts versehentlich freizugeben.
  3. Entwirf Klassen, deren Felder andere Objekte oder Objektlisten sind, arbeite mit Listen von Objekten und entscheide, wann enthaltene Objekte gemeinsam genutzt oder kopiert werden sollen.
    • Verwende andere Objekte zur Komposition einer Klasse
      Modelliere Hat-ein-Beziehungen mit Referenzfeldern, delegiere Aufgaben an die enthaltenen Objekte und entscheide, ob ein enthaltenes Objekt gemeinsam genutzt oder kopiert werden soll.
    • Speichere und verarbeite Objekte in Listen
      Füge Objekte in einer ArrayList hinzu, finde, ändere und entferne sie und erkläre, warum die Liste Referenzen statt Kopien enthält.
    • Entwickle eine Klasse zur Verwaltung einer Objektliste
      Schreibe eine Klasse, die eine Objektliste als Feld verwaltet, sie initialisiert, Methoden zur Arbeit mit ihrem Inhalt anbietet und die Liste selbst verbirgt.
  4. Unterscheide Identität von inhaltlicher Gleichheit, schreibe korrekte equals- und hashCode-Methoden und sage voraus, wie Listen, Mengen und Maps sie verwenden.
    • Unterscheide Identität und inhaltliche Gleichheit
      Erkläre, was == und equals bei primitiven Werten, Strings, Wrappern und eigenen Klassen vergleichen, und sage das Ergebnis jedes Vergleichs voraus.
    • Schreibe eine equals-Methode
      Überschreibe equals mit der richtigen Signatur und den richtigen Schritten, vergleiche jede Feldart korrekt und halte den allgemeinen Vertrag der Methode ein.
    • Implementiere hashCode passend zu equals
      Erkläre den hashCode-Vertrag, schreibe hashCode für eine Klasse, die equals überschreibt, und sage voraus, wie HashSet und HashMap sich mit und ohne diese Methode verhalten.
  5. Gliedere ein textbasiertes Programm so, dass eine Klasse mit dem Benutzer kommuniziert und andere Klassen die Daten und Regeln enthalten. Erweitere und prüfe es anschließend, ohne beides zu vermischen.
    • Gliedere ein Programm in eine Benutzeroberfläche und Klassen für die Programmlogik
      Entscheide, welcher Code zur Benutzeroberfläche und welcher zur Logik gehört, und verbinde beide über Konstruktorparameter und eine Startmethode.
    • Erweitere und prüfe ein Programm mit getrennter Benutzeroberfläche und Programmlogik
      Ergänze Funktionen, indem du die Logik erweiterst und von der Oberfläche aus aufrufst. Prüfe die Logik direkt mit bekannten Werten und gib jeder Klasse eine klar abgegrenzte Aufgabe.

Objektreferenzen in Java einfach erklärt

5 Kapitel

1

Verfolge Werte und Referenzen

Eine Variable und ein Objekt sind unterschiedliche Bestandteile eines Java-Programms. Wenn du verstehst, was jede Variable enthält, kannst du erklären, warum eine Zuweisung unabhängige Zahlen erzeugt, während eine andere über zwei Namen Zugriff auf dasselbe Objekt gibt. Beginne bei den kopierten Werten und verfolge dann die Änderungen Anweisung für Anweisung. Dasselbe Bild hilft dir, fehlende Objekte zu finden, Nullfehler zu lesen und zu entscheiden, ob eine Änderung gemeinsam sichtbar sein soll.

1.1 Was eine Variable enthält

Eine Java-Variable enthält einen Wert. Entscheidend ist, ob dieser Wert ein gewöhnliches Datum oder eine Referenz ist, die zu einem Objekt führt. Eine Zuweisung kopiert in beiden Fällen diesen Wert.

Primitive Daten und Objektreferenzen

Java hat acht primitive Datentypen: byte, short, int, long, float, double, char und boolean. Eine Variable eines primitiven Typs enthält ihr Datum direkt. Eine Referenzvariable enthält eine Referenz auf ein Objekt oder den besonderen Wert null. Klassen wie String, Scanner und ArrayList, deine eigenen Klassen und Arrays sind Referenztypen.

DeklarationInhalt der Variablen
int count = 5;Der Ganzzahlwert 5
Counter c = new Counter();Eine Referenz auf einen neuen Zähler
Counter c = null;Keine Objektreferenz

Bei new Counter() erzeugt Java ein Objekt, führt seinen Konstruktor aus und liefert eine Referenz. Die Zuweisung speichert diese Referenz in c. Zeichne die Variable als Kästchen mit einem Pfeil zu einem separaten Objekt. Jede Auswertung von new erzeugt ein eigenes Objekt, auch wenn die Argumente übereinstimmen.

Kopiere den Wert und verfolge das Ergebnis

Bei primitiven Werten lässt eine Änderung der kopierten Variablen die erste Variable unverändert:

int a = 5;
int b = a;
b = b + 1;

Die letzte Zeile liest 5 aus b, addiert 1 und speichert 6 in b. Der Wert in a bleibt 5.

Verwende für ein Referenzbeispiel diese kleine Klasse:

class Counter {
    private int value;

    Counter() { this.value = 0; }
    Counter(int value) { this.value = value; }
    void increase() { value++; }
    int value() { return value; }
}

Verfolge nun diese Anweisungen:

Counter first = new Counter();
Counter second = first;
second.increase();
System.out.println(first.value()); // 1

Nur die erste Zeile erzeugt einen Zähler. Die zweite Zeile kopiert die Referenz. Der Methodenaufruf ändert daher den einen Zähler, den beide Variablen erreichen. Für einen unabhängigen Zähler mit gleichen Daten schreibe new Counter(first.value()). Das Kopieren einer Variablen ruft keinen Konstruktor auf.

Wrapper und unveränderliche Strings

Jeder primitive Datentyp hat eine Wrapperklasse, etwa Integer, Double, Boolean oder Character. Wrappervariablen enthalten Referenzen oder null. Java kann automatisch zwischen einem primitiven Wert und seinem Wrapper umwandeln. Diese Umwandlungen heißen Autoboxing und Unboxing. Listen enthalten Referenzen, daher lautet der Typ einer Liste ganzer Zahlen ArrayList<Integer>.

Auch String ist ein Referenztyp, aber seine Objekte sind unveränderlich: Ihre Zeichen können nach der Erzeugung nicht geändert werden.

String s = "java";
String t = s;
t = t.toUpperCase();

Hier verweist s weiterhin auf „java“ und t auf „JAVA“. Die letzte Zeile weist das Ergebnis t zu. Sie ändert nicht den gemeinsam genutzten ursprünglichen String. String-Methoden können das ursprüngliche Objekt wiederverwenden, wenn keine Änderung nötig ist.

Die Ausgabe eines Objekts verwendet dessen Methode toString(). Ohne Überschreibung hat das geerbte Ergebnis eine Form wie Counter@7e9e5f8a: Klassenname, @ und ein hexadezimaler Hashcode. Definiere toString(), wenn die Ausgabe die Daten des Objekts zeigen soll.

Wichtige Erkenntnisse

  • Eine Zuweisung kopiert einen Wert: primitive Daten oder eine Objektreferenz.
  • Jede Auswertung von new erzeugt ein eigenes Objekt.
  • Das Kopieren einer Referenz führt zur gemeinsamen Nutzung des Objekts, kopiert aber nicht dessen Felder.
  • Wrapper und Strings sind Referenztypen; Strings sind unveränderlich.
  • Die Ausgabe eines Objekts verwendet toString().

1.2 Gemeinsam genutzte Objekte und veränderte Pfeile

Zwei Variablen, die auf dasselbe Objekt verweisen, sind Aliasse. Ein Alias bietet einen weiteren Weg zu vorhandenen Daten. Halte beim Nachvollziehen eines Programms die Variablen und die Objekte, die sie erreichen, auseinander.

Eine Zustandsänderung ändert Daten, eine Neuzuweisung eine Variable

Eine Zustandsänderung ändert den Zustand eines Objekts, etwa sein gespeichertes Gewicht. Eine Neuzuweisung legt einen anderen Wert in einer Variablen ab. Bei einer Referenzvariablen ändert sich dadurch ihr Pfeil.

Verwende eine Box, deren Gewicht in Kilogramm angegeben wird:

class Box {
    private double weight;
    Box(double weight) { this.weight = weight; }
    double getWeight() { return weight; }
    void setWeight(double weight) { this.weight = weight; }
}

Verfolge diese Abfolge:

Box a = new Box(2);
Box b = a;
b.setWeight(5);
b = new Box(7);
  1. Zeichne die Variable a so, dass sie auf Objekt A mit dem Gewicht 2 zeigt.
  2. Zeichne b so, dass es auf dasselbe Objekt A zeigt. Es gibt weiterhin eine Box.
  3. Ändere das Gewicht in Objekt A auf 5. Beide Wege führen nun zum Gewicht 5.
  4. Erzeuge Objekt B mit dem Gewicht 7 und richte nur b darauf aus.

Am Ende ist a.getWeight() gleich 5 und b.getWeight() gleich 7. Die vierte Anweisung setzt weder Objekt A zurück noch richtet sie a auf Objekt B aus.

AktionÄnderung im DiagrammWirkung über andere Aliasse
b.setWeight(5)Ein Feld im Objekt ändernSie sehen das neue Gewicht
b = new Box(7)Den Pfeil von b umlenkenIhre bisherigen Pfeile bleiben bestehen
b = nullDen Pfeil von b entfernenSie erreichen weiterhin ihre Objekte
Erreichbarkeit bestimmt die Lebensdauer eines Objekts

Ein Objekt ist erreichbar, solange das laufende Programm einer nutzbaren Referenzkette zu ihm folgen kann. Diese Kette kann über eine Variable, ein Objektfeld oder ein Listenelement führen. Das Verschwinden einer lokalen Variablen allein macht ein Objekt daher nicht unerreichbar, wenn eine Liste es noch enthält.

Setze den Ablauf mit a = null; fort. Objekt A mit einem Gewicht von 5 kg hat in diesem Beispiel nun keinen erreichbaren Pfad mehr. Es kann von der automatischen Speicherbereinigung entfernt werden. Java gibt den Speicher unerreichbarer Objekte automatisch frei, aber die Speicherbereinigung entscheidet, wann. Die Zuweisung verspricht keine sofortige Bereinigung. Objekt B bleibt über b erreichbar.

Du gibst Java-Objekte nicht von Hand frei. Wenn ein Programm seine letzte nutzbare Referenz auf ein Objekt verliert, verliert es auch die Möglichkeit, dessen Daten zu untersuchen oder zu ändern.

Finde die verborgenen Aliasse

Aliasse entstehen, wenn ein Objekt zu einer Liste hinzugefügt, an eine Methode übergeben, aus einer Methode zurückgegeben oder im Feld eines anderen Objekts gespeichert wird. Jede dieser Operationen kann eine Referenz weitergeben. Bevor du ein von anderswo erhaltenes Objekt änderst, bestimme, welcher andere Code es noch erreichen kann.

Ein häufiger Fehler beginnt mit dem Gedanken: „Ich habe die Variable kopiert, also habe ich meine eigene Box.“ Verwende ein neues Objekt, wenn Änderungen unabhängig sein sollen. Um den Fehler zu finden, zähle Objekterzeugungen und Zuweisungen getrennt, beschrifte jedes Objekt einmal und gehe alle Anweisungen der Reihe nach durch. Zeichne kein Objekt neu, nur weil eine zweite Variable darauf zeigt.

Wichtige Erkenntnisse

  • Aliasse führen zu einem Objekt und teilen daher dessen Zustandsänderungen.
  • Eine Neuzuweisung lenkt eine Variable um und lässt andere Aliasse unverändert.
  • Verfolge Felder und Referenzpfeile getrennt.
  • Ein unerreichbares Objekt kann von der automatischen Speicherbereinigung entfernt werden; der Zeitpunkt ist nicht festgelegt.

1.3 Kein Objekt: null und sichere Prüfungen

Der Wert null bedeutet, dass eine Referenz zu keinem Objekt führt. Das ist für ein fehlendes Ergebnis nützlich, aber ein Programm muss das Fehlen behandeln, bevor es versucht, ein Objekt zu verwenden.

Fehlend ist nicht leer

String text = ""; verweist auf einen echten String der Länge 0. String text = null; verweist auf keinen String, sodass es kein Objekt gibt, auf dem length() aufgerufen werden kann.

Eine Variable eines primitiven Typs kann kein null enthalten: int x = null; lässt sich nicht kompilieren. Ein Wrapper kann diesen Wert enthalten: Integer x = null; lässt sich kompilieren. Die Umwandlung dieses Wrappers in einen primitiven Wert mit int y = x; löst eine NullPointerException aus, weil Unboxing ein Wrapperobjekt benötigt.

Felder erhalten beim Erzeugen eines Objekts anfängliche Standardwerte. Referenzfelder beginnen mit null, numerische Felder mit 0 und boolesche Felder mit false, bis bei der Initialisierung andere Werte zugewiesen werden. Lokale Variablen haben keinen Standardwert. Das Lesen einer lokalen Variablen vor ihrer Zuweisung führt zu einem Kompilierfehler wie „variable might not have been initialized“.

Finde die fehlende Referenz

Der Aufruf einer Instanzmethode über null, der Zugriff auf ein Instanzfeld darüber oder das Lesen einer Arraylänge darüber löst zur Laufzeit eine NullPointerException aus. Zum Beispiel:

String name = null;
System.out.println(name.length());

Lies die Zeilennummer der Ausnahme und bestimme dann, welche Referenz in dieser Zeile fehlte. Hilfreiche Meldungen sind seit Java 15 standardmäßig aktiviert. Eine Meldung kann Cannot invoke "SimpleDate.getYear()" because "this.birthday" is null lauten. Lokale Namen können je nach Kompilierung als Platzhalter wie <local4> erscheinen. Verfolge den Ablauf zurück bis zu dem Konstruktor, der Zuweisung oder der Suche, die das Objekt hätte liefern sollen.

Die Ausgabe einer String-Variablen mit dem Wert null gibt „null“ aus; ihre Konkatenation erzeugt Text wie „Hi null“. Diese Operationen müssen keine Instanzmethode über diese Variable aufrufen. Sie können fehlende Daten sichtbar machen, bevor ein späterer Methodenaufruf fehlschlägt.

Prüfe vor der Verwendung

Der Operator && wertet mit Kurzschluss aus: Er wertet die rechte Seite nur aus, wenn die linke wahr ist.

if (name != null && name.length() > 0) {
    System.out.println(name);
}

Ist name gleich null, ist die erste Prüfung falsch und length() wird übersprungen. Werden die beiden Prüfungen umgekehrt, entfällt dieser Schutz: Der Methodenaufruf würde zuerst ausgeführt.

ZweckSicherer Ausdruck
Auf Fehlen prüfenname == null
Auf Vorhandensein prüfenname != null
Einen festen Befehl vergleichen"quit".equals(command)

Der feste String ist ein bekanntes Objekt, sodass sein equals den Wert null entgegennehmen und false zurückgeben kann. command.equals("quit") schlägt fehl, wenn command null ist. Ebenso ist x.equals(null) keine Möglichkeit, die Existenz von x zu prüfen: Der Aufruf setzt bereits ein Objekt voraus.

Mache das Fehlen zu einer klaren Entwurfsentscheidung

Initialisiere ein Listenfeld mit new ArrayList<>(), sodass sofort eine leere Collection verfügbar ist. Gibt eine Suche null für „nicht gefunden“ zurück, dokumentiere dieses Ergebnis und prüfe es an der Aufrufstelle. Fülle nicht jedes Feld mit beliebigen Objekten, nur um eine Ausnahme zu verhindern. Stelle das Objekt bereit, das das Programm benötigt, oder gib dem Fehlen eine klare Bedeutung, die der aufrufende Code behandelt.

Wichtige Erkenntnisse

  • null bedeutet, dass kein Objekt vorhanden ist; ein leerer String ist ein verwendbares Objekt.
  • Felder erhalten Standardwerte, lokalen Variablen muss vor dem Lesen ein Wert zugewiesen werden.
  • Das Dereferenzieren von null und das Unboxing eines Wrappers mit dem Wert null schlagen zur Laufzeit fehl.
  • Setze die Prüfung auf null in && vor den Zugriff auf das Mitglied.
  • Rufe Vergleiche mit festem Text auf dem bekannten String auf.
  • Initialisiere Collections und behandle fehlende Suchergebnisse bewusst.
2

Verwende Objekte über Methodenaufrufe hinweg

Methoden ermöglichen die Zusammenarbeit von Objekten. Ein Aufruf kann ein gemeinsam genutztes Objekt lesen oder ändern, ein unabhängiges Ergebnis erzeugen oder ein bereits anderswo gespeichertes Objekt zurückgeben. Diese Entscheidungen wirken sich darauf aus, was der aufrufende Code nach dem Aufruf sieht. Verfolge die Referenzen in Parametern und Rückgaben ausdrücklich und platziere Vergleiche in der Klasse, der die Daten gehören. Entscheide dann, welche Methoden vorhandenen Zustand teilen und welche ihn durch eine Kopie schützen sollen.

2.1 Parameter erhalten kopierte Werte

Java übergibt jedes Argument per Wertübergabe. Ein Parameter ist eine neue Variable, die eine Kopie des Argumentwerts erhält. Bei einem Objektargument ist der kopierte Wert eine Referenz.

Den Parameter oder das Objekt ändern?

Ein Parameter eines primitiven Typs hat seine eigenen Daten:

static void addTen(int n) {
    n = n + 10;
}

Ist x gleich 5, lässt der Aufruf addTen(x) den Wert von x bei 5. Nur der lokale Parameter n wird zu 15.

Ein Referenzparameter kann dasselbe Objekt erreichen wie der aufrufende Code. Angenommen, Person.growOlder() erhöht das gespeicherte Alter:

static void celebrate(Person p) {
    p.growOlder();
}

static void replace(Person p) {
    p = new Person("Bo", 30);
}

Während celebrate(ana) sind der Parameter p und die Variable ana Aliasse. Das geänderte Alter bleibt danach sichtbar. Während replace(ana) lenkt die Zuweisung an p nur diesen Parameter um. Die Variable ana des aufrufenden Codes verweist weiterhin auf die ursprüngliche Person. Dieselbe Einschränkung gilt für eine Methode, die zwei Referenzparameter vertauscht: Sie vertauscht ihre eigenen Kopien, nicht die Variablen des aufrufenden Codes.

Innerhalb einer MethodeBeobachtung im aufrufenden Code
Einem primitiven Parameter einen Wert zuweisenKeine Änderung der aufrufenden Variablen
Das Objekt des Parameters ändernGeänderter Zustand des gemeinsam genutzten Objekts
Einem Referenzparameter einen neuen Wert zuweisenKeine Änderung der aufrufenden Variablen
Ein Objekt einer anderen Klasse entgegennehmen

Ein Tor kann die Größe einer Person lesen und einen eigenen Zähler für Prüfungen führen. Diese Klasse erwartet eine Person ungleich null mit einer Methode getHeight(), die Zentimeter zurückgibt:

class HeightGate {
    private int minHeight;
    private int checks;

    HeightGate(int minHeight) {
        this.minHeight = minHeight;
    }

    boolean allowIn(Person p) {
        checks++;
        return p.getHeight() >= minHeight;
    }

    int checks() { return checks; }
}

Bei einem auf 120 cm eingestellten Tor ergeben Prüfungen von Personen mit 132 cm und 115 cm die Werte true und false. Das Tor meldet danach 2 Prüfungen. Es liest die Größe der Person über eine öffentliche Methode und ändert seinen eigenen Zähler.

Ein weiteres Objekt derselben Klasse entgegennehmen

Innerhalb von SimpleDate.before(SimpleDate other) ist this das Datumsobjekt, auf dem die Methode aufgerufen wird, und other das Argument. Eine Klasse kann die privaten Felder jedes Objekts derselben Klasse lesen.

Vergleiche bei den Feldern day, month und year der Reihe nach:

boolean before(SimpleDate other) {
    if (year != other.year) {
        return year < other.year;
    }
    if (month != other.month) {
        return month < other.month;
    }
    return day < other.day;
}

Beim 14. März 2001 und 2. November 2001 stimmen die Jahre überein und Monat 3 ist kleiner als Monat 11. Das erste Datum liegt also früher. Gleiche Datumsangaben ergeben false, weil keine vor der anderen liegt.

Person.olderThan(other) kann an birthday.before(other.birthday) delegieren. Der Datumsvergleich gehört zu den Datumsdaten. Methoden, die Instanzmethoden auf einem Parameter aufrufen, benötigen ein Argument ungleich null. Prüfe es entweder oder halte diese Anforderung in der Dokumentation der Methode fest.

Wichtige Erkenntnisse

  • Jeder Parameter erhält einen kopierten Wert, auch ein Referenzparameter.
  • Eine Zustandsänderung kann ein gemeinsam genutztes Objekt betreffen; eine Neuzuweisung an den Parameter kann die Variable des aufrufenden Codes nicht ersetzen.
  • this bezeichnet das Empfängerobjekt; ein Objektparameter bezeichnet das Argument.
  • Eine Klasse kann auf private Felder eines anderen Objekts derselben Klasse zugreifen.
  • Delegiere Vergleiche an die Klasse, der die relevanten Daten gehören.

2.2 Ein neues oder ein vorhandenes Objekt zurückgeben

Eine Methode, die ein Objekt zurückgibt, gibt eine Referenz zurück. Ihr Typ sagt dir, welche Art von Objekt zurückgegeben werden kann, aber nicht, ob das Objekt neu ist, gemeinsam genutzt wird oder fehlt. Der Zweck der Methode muss diese Entscheidung deutlich machen.

Ein Ergebnis erzeugen

Eine Fabrikmethode liefert ein Objekt. Sie kann eines erzeugen oder ein vorhandenes wiederverwenden. Hier erzeugt jeder Aufruf bewusst ein eigenes Ticket:

class TicketMachine {
    private int nextNumber = 1;

    Ticket issue() {
        Ticket t = new Ticket(nextNumber);
        nextNumber++;
        return t;
    }
}

Der Automat erwartet einen Ticket-Konstruktor, der die Nummer speichert. Drei Aufrufe erzeugen drei Tickets mit den Nummern 1, 2 und 3. Die Rückgabe einer lokalen Variablen ist sicher: Ihre Referenz wird an den aufrufenden Code kopiert, der das Objekt erreichbar halten kann, nachdem die lokale Variable ihren Gültigkeitsbereich verlassen hat.

Eine Kopiermethode verspricht ein unabhängiges Objekt. In der Klasse Counter:

Counter copy() {
    return new Counter(this.value);
}

Nach Counter a = new Counter(4); Counter b = a.copy(); b.increase(); ist a.value() gleich 4 und b.value() gleich 5. Würde a.copy() durch a ersetzt, würde der Zähler gemeinsam genutzt.

Eine Methode kann auch ein berechnetes Objekt erzeugen. Für einen Point mit den double-Feldern x und y:

Point midpoint(Point other) {
    return new Point((x + other.x) / 2.0,
                     (y + other.y) / 2.0);
}

Die Punkte (2|4) und (6|10) ergeben (4,0|7,0). Die Methode berechnet jede Koordinate und übergibt beide an einen neuen Punkt. Keiner der ursprünglichen Punkte ändert sich.

Ein bereits gespeichertes Objekt zurückgeben

Die Methode findByName eines Registers sollte die passende gespeicherte Person zurückgeben, wenn der aufrufende Code mit diesem Eintrag arbeiten muss. Die Erzeugung von new Person(name, 0) würde erfundene Ersatzdaten zurückgeben und spätere Änderungen vom Register entkoppeln.

Eine Suche kann null zurückgeben, wenn kein Treffer existiert:

Person p = register.findByName("Lin");
if (p != null) {
    System.out.println(p);
} else {
    System.out.println("Not found");
}

Ist p die gespeicherte Person, ändern Änderungen über p auch die Person, die über das Register erreichbar ist. Eine return-Anweisung kopiert das Objekt nicht.

Veränderlichen internen Zustand unter Kontrolle halten

Angenommen, Person.getBirthday() gibt sein Feld SimpleDate zurück und dieses Datum hat Setter. Der aufrufende Code kann dann den Geburtstag der Person ändern, ohne eine Person-Methode aufzurufen. Das ist eine Freigabe veränderlichen Zustands.

Um das Feld zu schützen, gib ein neues Datum zurück, das aus seinen Werten aufgebaut wird:

SimpleDate getBirthday() {
    return new SimpleDate(birthday.getDay(),
                          birthday.getMonth(),
                          birthday.getYear());
}

Diese Version setzt voraus, dass ein Geburtstagsobjekt initialisiert wurde. Andere Entwürfe verwenden ein unveränderliches Datum oder bieten gezielte Abfragen ohne Datums-Getter an. Entscheide für jede Methode über das Verhalten: find gibt normalerweise einen gespeicherten Eintrag zur gemeinsamen Nutzung zurück, während copy Unabhängigkeit schafft. Der kurze Name eines Getters allein verspricht keinen Schutz.

Wichtige Erkenntnisse

  • Die Rückgabe eines Objekts übergibt eine Referenz an den aufrufenden Code.
  • Fabrikmethoden können Objekte erzeugen oder wiederverwenden; eine Kopiermethode erzeugt ein separates Objekt.
  • Suchmethoden können vorhandene Einträge gemeinsam nutzbar machen und müssen ein fehlendes Ergebnis festlegen.
  • Die Rückgabe eines veränderlichen Felds kann es dem aufrufenden Code ermöglichen, die Methoden des Besitzerobjekts zu umgehen.
3

Verbinde Objekte und Listen

Ein größeres Modell ist ein Netzwerk aus Objekten: Eine Person hat ein Datum, eine Playlist hat eine Liste und die Liste enthält Songs. Jede Verbindung ist eine Referenz, die Folgen für gemeinsame Nutzung und Änderungen hat. Baue diese Verbindungen bewusst auf, delegiere Aufgaben an das Objekt, das sie versteht, und sichere die Regeln für Collections hinter passenden Methoden. Eine kopierte Collection kann ihre eigene Zusammensetzung schützen und trotzdem alle enthaltenen Objekte gemeinsam nutzen.

3.1 Objekte mit Referenzfeldern

Komposition baut eine Klasse mithilfe von Feldern auf, deren Typen andere Klassen sind. Sie drückt eine „Hat-ein“-Beziehung aus: Eine Person hat einen Geburtstag; ein Kurs hat eine Lehrkraft. Das äußere Objekt speichert eine Referenz auf das enthaltene Objekt.

Objekte erzeugen und verbinden

Ein Konstruktor kann ein vorhandenes Datum entgegennehmen:

class Person {
    private String name;
    private SimpleDate birthday;

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

    boolean olderThan(Person other) {
        return birthday.before(other.birthday);
    }

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

Der Aufruf new Person("Ana", new SimpleDate(14, 3, 2001)) erzeugt zuerst ein Datum und übergibt dessen Referenz an die Person. Alternativ kann ein Person-Konstruktor Tag, Monat und Jahr entgegennehmen und das Datum in seinem Rumpf erzeugen. Bei diesem Ansatz behält der aufrufende Code keine Referenz auf das neu erzeugte Datum.

Aufgaben an das enthaltene Objekt delegieren

Der Aufruf birthday.before(other.birthday) fordert das Datumsobjekt auf, Datumsangaben zu vergleichen. Person muss die Vergleiche von Jahr, Monat und Tag nicht wiederholen. Auch die Konkatenation in toString() delegiert: Java ruft toString() des Geburtstagsobjekts auf, um dessen Text zu erhalten.

Ein Datumsobjekt kann bei der Altersberechnung helfen. Beginne mit dem aktuellen Jahr minus dem Geburtsjahr. Bilde dann das Datum des diesjährigen Geburtstags und ziehe eins ab, wenn das aktuelle Datum davor liegt. Eine am 14. März 2001 geborene Person ist am 1. Januar 2026 24: Die Jahresdifferenz beträgt 25, aber der Geburtstag steht noch bevor. Am 14. März 2026 ist der Geburtstag erreicht und das Alter beträgt 25. Bleibt der Datumsvergleich in SimpleDate, müssen seine Regeln nicht in jeder Person-Methode wiederholt werden.

Entscheiden, ob das innere Objekt gemeinsam genutzt wird

Der Konstruktor oben speichert sein Argument direkt. Erhalten zwei Personen ein veränderliches SimpleDate, zeigen beide Geburtstagsfelder auf dasselbe Datum. Eine Änderung dieses Datums betrifft beide Personen.

Entscheidung im KonstruktorFolge für die Referenz
Die übergebene Datumsreferenz speichernAufrufender Code und Person können das Datum gemeinsam nutzen
Ein neues Datum aus den übergebenen Werten erzeugenDie Person beginnt mit einem eigenen Datum
Ein unveränderliches Datum speichernGemeinsame Nutzung kann dessen Datumswerte nicht ändern

Ein defensiver Konstruktor kann Tag, Monat und Jahr des übergebenen Datums kopieren. Ein unveränderliches Datum hat keine zustandsändernden Methoden und behält feste Werte; einfache primitive Felder können final sein. Das Kopieren bei der Eingabe ist auch dann wichtig, wenn ein Getter später eine Kopie zurückgibt, weil der ursprüngliche aufrufende Code das übergebene Objekt noch enthalten kann.

Optionale Verbindungen behandeln

Ein Course hat möglicherweise noch keine Lehrkraft. Ist das ein sinnvoller Zustand, kann teacherName() das Feld prüfen und „noch keine Lehrkraft“ zurückgeben. Andernfalls verlange eine Lehrkraft bei der Konstruktion.

In course.getTeacher().getName() benötigt jeder Instanzaufruf ein Objekt: Ein Kurs mit dem Wert null schlägt bei getTeacher() fehl, eine Lehrkraft mit dem Wert null bei getName(). Eine Methode des Kurses kann diese Prüfung auf Fehlen an einer Stelle bündeln und vermeiden, dass jeder aufrufende Code dieselbe Kette kennen muss.

Wichtige Erkenntnisse

  • Komposition verbindet ein äußeres Objekt über Felder mit anderen Objekten.
  • Delegiere Operationen an das Objekt, dem die relevanten Daten gehören.
  • Das Speichern einer übergebenen Referenz auf ein veränderliches Objekt kann Zustand mit dem aufrufenden Code teilen.
  • Kopien oder unveränderliche innere Objekte können unabhängigen Zustand schützen.
  • Optionale Referenzfelder brauchen eine klare Regel für den Umgang mit null.

3.2 Listen enthalten Referenzen auf ihre Elemente

Eine ArrayList<Person> ist ein Objekt, dessen Elemente Referenzen auf Personen enthalten. Die Liste verwaltet die Reihenfolge ihrer Einträge; jede Person verwaltet ihre eigenen Felder. Das sind unterschiedliche Arten von Änderungen.

Ein Objekt hinzufügen und wieder erreichen
ArrayList<Person> people = new ArrayList<>();
people.add(ana);
people.get(0).growOlder();

Dieser Code benötigt import java.util.ArrayList; und eine vorhandene Person ana ungleich null. add kopiert die Referenz. Der Aufruf von growOlder() erreicht dieselbe Person wie ana, sodass die Altersänderung über beide Wege sichtbar ist. Die Liste erzeugt hier keine zweite Person.

Du kannst ein Objekt beim Hinzufügen erzeugen: people.add(new Person("Lin", 1998));. Dabei wird ein Person-Konstruktor verwendet, der einen Namen und ein Geburtsjahr entgegennimmt. Die Liste bietet dem Programm dann über get oder eine Schleife einen Weg zu dieser neuen Person. Typargumente von Listen müssen Referenztypen sein. Verwende daher ArrayList<Integer> für ganze Zahlen.

Mit Objekten rechnen und ein Objekt auswählen

Angenommen, eine Liste enthält Pakete mit den Gewichten 2,5 kg, 4,0 kg und 1,5 kg. Eine Schleife addiert das Gewicht jedes Pakets:

double total = 0;
for (Parcel p : parcels) {
    total += p.getWeight();
}

Die laufenden Summen betragen 2,5, 6,5 und 8,0 kg. Um das schwerste Paket auszuwählen, brauchst du neben einem Vergleich auch eine Referenz:

Parcel heaviest = parcels.get(0);
for (Parcel p : parcels) {
    if (p.getWeight() > heaviest.getWeight()) {
        heaviest = p;
    }
}

Diese Auswahl setzt eine nicht leere Liste voraus. Das Ergebnis ist das Paketobjekt mit 4,0 kg, sodass der aufrufende Code auch dessen andere Methoden verwenden kann. Lege für eine leere Liste ein Ergebnis für den Fall fest, dass nichts vorhanden ist, bevor du Element 0 liest.

Jeden Eintrag durchsuchen

Eine Namenssuche in einer Klasse, die people speichert, kann so aussehen:

Person findByName(String name) {
    for (Person p : people) {
        if (p.getName().equals(name)) {
            return p;
        }
    }
    return null;
}

Diese Version erwartet Personen ungleich null mit Namen ungleich null. Der erste Treffer wird sofort zurückgegeben. Das abschließende return null gehört hinter die Schleife: Ein fehlgeschlagener Vergleich bei der ersten Person schließt spätere Treffer nicht aus.

contains, indexOf und remove(Object) suchen mithilfe von equals. Erbt Person eine identitätsbasierte Gleichheitsprüfung, stimmt eine neu erzeugte Person nicht schon deshalb mit einer vorhandenen überein, weil ihre Felder übereinstimmen.

Einen Listeneintrag entfernen, ohne das Objekt zu zerstören

Ein Objekt kann zweimal in einer Liste oder in mehreren Listen vorkommen. Das Entfernen eines Eintrags zerstört weder das Objekt noch entfernt es andere Referenzen darauf.

Entferne während einer for-each-Traversierung keine Einträge direkt aus derselben Liste. Das kann eine ConcurrentModificationException auslösen. Sammle zuerst die zu entfernenden Einträge und entferne sie dann:

ArrayList<Person> toRemove = new ArrayList<>();
for (Person p : people) {
    if (p.getName().equals("Lin")) {
        toRemove.add(p);
    }
}
people.removeAll(toRemove);

Die Traversierung endet, bevor sich die Zusammensetzung der Liste ändert. removeAll verwendet die Gleichheitsprüfung und entfernt daher Einträge, die den gesammelten Einträgen gleich sind.

Wichtige Erkenntnisse

  • Das Hinzufügen zu einer Liste führt zur gemeinsamen Nutzung einer Objektreferenz.
  • Eine Schleife kann eine Summe berechnen oder eine Referenz auf ein ausgewähltes Objekt behalten.
  • Eine erfolglose Suche gibt erst ein Ergebnis zurück, nachdem alle Kandidaten geprüft wurden.
  • Operationen zur Listenzugehörigkeit hängen von equals ab.
  • Das Entfernen eines Eintrags lässt andere Referenzen unverändert.
  • Sammle zu entfernende Einträge zuerst und ändere die Liste nach der Traversierung.

3.3 Eine Klasse, die eine Liste verwaltet

Eine Klasse kann eine Liste in einem privaten Feld verwalten und Operationen anbieten, die ihrem Zweck entsprechen. Eine Playlist speichert Songs, zählt sie und berechnet die Dauer. Aufrufender Code sollte ihre Liste nicht direkt verwalten müssen.

Die Collection initialisieren und kapseln

Diese Playlist erwartet Song-Objekte ungleich null mit einer Methode seconds():

import java.util.ArrayList;

class Playlist {
    private String name;
    private ArrayList<Song> songs;

    Playlist(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    void add(Song song) { songs.add(song); }
    int count() { return songs.size(); }

    int totalSeconds() {
        int total = 0;
        for (Song song : songs) {
            total += song.seconds();
        }
        return total;
    }
}

Ohne new ArrayList<>() bleibt das Referenzfeld null; der erste Aufruf von add schlägt dann fehl. Eine leere Liste ist bereits ein verwendbares Objekt der Größe 0.

Für Songs mit einer Dauer von 210 s, 185 s und 247 s gibt count() den Wert 3 zurück. Die Summe wächst von 210 auf 395 und dann auf 642 s. Die Ganzzahldivision durch 60 ergibt 10 Minuten mit einem Rest von 42 Sekunden. Eine Methode longest() kann den Song mit 247 s auswählen und zurückgeben. Dabei verwendet sie dasselbe Muster zur Auswahl einer Referenz wie beim schwersten Paket. Lege für eine leere Playlist ein Ergebnis für den Fall fest, dass kein Song vorhanden ist.

Die Listenzugehörigkeit durch Methoden schützen

Ist songs privat, verhindert das direkten Feldzugriff, aber return songs; gibt genau diese Liste nach außen frei. Aufrufender Code könnte clear() aufrufen und alle Regeln der Playlist umgehen.

Gezielte Operationen wie add, count und totalSeconds belassen die Verantwortung bei der Playlist. Ihre Methode add könnte eine maximale Größe durchsetzen oder Duplikate mit songs.contains(song) ablehnen. Für inhaltsbasierte Duplikatprüfungen muss equals der Song-Klasse festlegen, welche Felder zwei Songs gleich machen.

Erhält ein Konstruktor eine anfängliche Liste, kopiere ihre Struktur mit this.songs = new ArrayList<>(initialSongs);. Würde der Parameter direkt gespeichert, könnte der aufrufende Code einen Alias auf die interne Liste der Playlist behalten.

Eine neue Liste kann Songs weiterhin gemeinsam nutzen

Eine Abfrage kann new ArrayList<>(songs) zurückgeben. Das ist eine flache Kopie: ein neues Listenobjekt, das die vorhandenen Song-Referenzen enthält.

Aktion des aufrufenden Codes an der zurückgegebenen KopieWirkung auf die Playlist
Einen Eintrag entfernenDie Zusammensetzung der Playlist bleibt gleich
Die Kopie leerenDie Zusammensetzung der Playlist bleibt gleich
Einen gemeinsam genutzten Song ändernDie Playlist sieht den geänderten Zustand dieses Songs

Eine Abfrage songsLongerThan(200) erzeugt eine neue Ergebnisliste, durchläuft die gespeicherten Songs und fügt diejenigen hinzu, deren Dauer 200 s überschreitet. Für die drei Songs oben gibt sie die Songs mit 210 s und 247 s zurück. Sollen auch die Song-Werte unabhängig sein, kopiere die veränderlichen Songs oder mache Song unveränderlich.

Wichtige Erkenntnisse

  • Ein Besitzerobjekt, seine Liste und deren Elemente sind separate Objekte.
  • Initialisiere die Liste vor ihrer Verwendung.
  • Biete Operationen an, die die Collection-Regeln des Besitzerobjekts durchsetzen.
  • Kopiere übergebene Listen, um einen externen Alias auf die interne Liste zu verhindern.
  • Eine flache Listenkopie nutzt ihre Elementobjekte gemeinsam mit der ursprünglichen Liste.
4

Definiere, was Objekte gleich macht

Zwei Objekte können dasselbe beschreiben und trotzdem separate Objekte bleiben. Java bietet unterschiedliche Prüfungen für Identität und Inhalt, und Collections hängen von der Entscheidung deiner Klasse ab. Wähle zuerst die Felder, die Gleichheit definieren, und implementiere dann equals und hashCode gemeinsam. Ihre Verträge erklären, warum eine Liste ein gleiches Objekt finden kann, warum eine Hashkollision erlaubt ist und warum das Ändern eines gespeicherten Schlüssels eine Suche beeinträchtigen kann.

4.1 Identität und gleicher Inhalt

Identität fragt, ob zwei Referenzen zum selben Objekt führen. Inhaltliche Gleichheit fragt, ob Objekte nach den Regeln der Klasse dieselben relevanten Daten haben. Zwei separate Buchobjekte können denselben Titel und dasselbe Erscheinungsjahr beschreiben, ohne identisch zu sein.

Den richtigen Vergleich wählen
Verglichene WerteBedeutung von ==Bedeutung von equals
Primitive WertePrimitive Werte vergleichenKeine Instanzmethode auf einem primitiven Wert
ReferenzenDasselbe Objekt oder beide nullDie Gleichheitsregel der Klasse
StringsDasselbe String-ObjektÜbereinstimmender Zeicheninhalt
Klasse, die Object.equals erbtDasselbe ObjektDasselbe Objekt

Jede Klasse erbt equals(Object) von Object. Diese Standardversion prüft die Identität. Java untersucht nicht automatisch alle Felder einer neuen Klasse.

String a = "java";
String b = new String("java");
System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

new String erzeugt ein separates Objekt, während String.equals dessen Zeichen vergleicht. Der Textvergleich beachtet die Groß- und Kleinschreibung: "Java".equals("java") ist false. equalsIgnoreCase vergleicht ohne Beachtung der Groß- und Kleinschreibung, wenn das der gewünschten Regel entspricht.

Warum Identität manchmal den Inhalt zu vergleichen scheint

Gleiche Stringliterale nutzen ein String-Objekt gemeinsam, daher ist "java" == "java" wahr. Das kann in einem kleinen Test einen Fehler verbergen. Text aus einer Eingabe oder Text, der während der Programmausführung aufgebaut wird, muss dieses Objekt nicht gemeinsam nutzen. Verwende für den Inhalt equals, unabhängig davon, woher der Text stammt.

Wrappervergleiche haben eine ähnliche Falle:

Integer a = 127, b = 127;
Integer c = 1000, d = 1000;
System.out.println(a == b);      // true
System.out.println(c.equals(d)); // true

Autoboxing verwendet Integer-Objekte für Werte von −128 bis einschließlich 127 wieder. Ob größere Werte ein Objekt gemeinsam nutzen, ist keine portable Regel für Zahlenvergleiche. In einer Standardumgebung sind die beiden Wrapper für 1000 separate Objekte, ein größerer Cache ist aber zulässig. Verwende equals der Wrapper für gleiche Wrapperwerte oder vergleiche primitive Werte. Ist eine Seite von == ein int, führt Java Unboxing für den Wrapper aus und vergleicht Zahlenwerte; das Unboxing eines Wrappers mit dem Wert null schlägt fehl.

Entscheiden, welche Felder Gleichheit definieren

Für einen Buchkatalog könnten Titel und Erscheinungsjahr die Gleichheit definieren, während die Person ignoriert wird, die das Buch gerade ausgeliehen hat. Diese ausgewählten Felder müssen die Gleichheitsmethode bestimmen. Ein anderes Programm könnte stattdessen einen eindeutigen Identifikator wählen; die geeignete Regel hängt davon ab, was seine Objekte darstellen.

Eine ArrayList verwendet equals in contains, indexOf und remove(Object). Angenommen, Book speichert nur Felder und erbt equals. Das Hinzufügen eines new Book("Dune", 1965) macht contains(new Book("Dune", 1965)) nicht wahr: Der zweite Ausdruck erzeugt ein anderes Objekt. Wird die Gleichheitsprüfung anhand von Titel und Jahr überschrieben, kann eine Suche diesen gleichen Inhalt finden.

HashSet und HashMap verwenden außerdem hashCode, um die möglichen Treffer vor der Gleichheitsprüfung einzugrenzen. Die Definition inhaltlicher Gleichheit verlangt deshalb auch eine passende Hashcode-Regel.

Wichtige Erkenntnisse

  • Bei Referenzen prüft == die Identität, während equals der Regel der Klasse folgt.
  • Object.equals vergleicht die Identität, bis die Methode überschrieben wird.
  • Verwende String.equals für Textinhalte.
  • Gemeinsam genutzte Literale und zwischengespeicherte Wrapper können Identitätsprüfungen irreführend machen.
  • Wähle gleichheitsrelevante Felder bewusst; Collections hängen von dieser Wahl ab.

4.2 equals korrekt implementieren

Eine Gleichheitsmethode muss das geerbte equals(Object) ersetzen und die Felder vergleichen, die die Bedeutung deines Objekts bestimmen. Die richtige Signatur ist ebenso wichtig wie die Vergleiche innerhalb der Methode.

Ein vollständiges Muster für die Gleichheitsprüfung

Diese Klasse verwendet Titel und Jahr, erlaubt einen Titel mit dem Wert null und hält ihre gleichheitsrelevanten Felder unveränderlich:

import java.util.Objects;

final class Book {
    private final String title;
    private final int year;

    Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Book)) {
            return false;
        }
        Book book = (Book) other;
        return year == book.year
            && Objects.equals(title, book.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, year);
    }
}

Lies equals in vier Schritten. Die Identitätsprüfung akzeptiert dasselbe Objekt sofort. Die Typprüfung lehnt alles ab, was kein Buch ist, einschließlich null. Die explizite Typumwandlung liefert eine Book-Variable, über die die Felder gelesen werden können. Der letzte Ausdruck prüft beide gleichheitsrelevanten Felder.

Der Parametertyp ist Object, nicht Book. Mit equals(Book other) überlädst du den Namen mit einer weiteren Methode und lässt das geerbte equals(Object) unverändert. Ein direkter Aufruf kann dann zu funktionieren scheinen, während ArrayList.contains weiterhin die Identität prüft. Dank @Override erkennt der Compiler diesen Signaturfehler.

Ab Java 16 lassen sich Typprüfung und explizite Typumwandlung verbinden: if (!(other instanceof Book book)) { return false; }. Nach erfolgreicher Prüfung steht book für Feldvergleiche zur Verfügung.

Vergleiche an die Feldtypen anpassen

Verwende == für ganzzahlige Felder sowie Zeichen- und boolesche Felder. Verwende equals eines Objekts für dessen Inhalt. Für ein Objektfeld, das null sein kann, gibt Objects.equals(a, b) true zurück, wenn beide Werte null sind, false, wenn nur einer null ist, und verwendet ansonsten die Gleichheitsprüfung.

Für double-Felder sorgt Double.compare(x, other.x) == 0 für eine konsistente Behandlung von Sonderwerten: Zwei NaN-Werte gelten als gleich und positive und negative null werden unterschieden. Kombiniere diese Wahl mit einer passenden Hashcode-Regel. Der direkte Gleitkommavergleich mit == behandelt NaN anders.

Den Gleichheitsvertrag prüfen

Für Referenzen ungleich null muss die Gleichheitsprüfung folgende Eigenschaften haben:

  • Reflexivität: x.equals(x) ist true.
  • Symmetrie: x.equals(y) und y.equals(x) stimmen überein.
  • Transitivität: Wenn x gleich y und y gleich z ist, ist x gleich z.
  • Konsistenz: Wiederholte Aufrufe stimmen überein, solange die verglichenen Daten unverändert bleiben.
  • False für null: x.equals(null) ist false.

Zwei Bücher für „Dune“, 1965, werden als gleich verglichen. Ändert sich eines der Jahre auf 1984, sind sie ungleich. Ein Buch darf nicht gleich einem String mit seinem Titel sein: Der String würde das Buch ablehnen und damit die Symmetrie verletzen.

Manche Implementierungen verwenden other == null || getClass() != other.getClass() für die Typprüfung. Diese Prüfung lehnt unterschiedliche Laufzeitklassen ab. instanceof kann Unterklassen akzeptieren; bei der final-Klasse oben gibt es keine Unterklasse und beide Prüfungen stimmen überein.

Wichtige Erkenntnisse

  • Überschreibe die Methode mit public boolean equals(Object other) und @Override.
  • Prüfe Identität und Typ, wandle die Referenz um und vergleiche die gleichheitsrelevanten Felder.
  • Verwende nullsichere Objektvergleiche, wenn Felder fehlen können.
  • Beachte Reflexivität, Symmetrie, Transitivität, Konsistenz und die Ablehnung von null.
  • equals(Book) überlädt die Methode und ersetzt equals(Object) nicht.
  • Die Wahl der Typprüfung ist wichtig, wenn Unterklassen erlaubt sind.

4.3 Passende Hashcodes und stabile Schlüssel

Ein Hashcode ist eine ganze Zahl, die eine Suche eingrenzt. HashSet und HashMap verwenden ihn, um mögliche Treffer zu finden, und bestätigen eine Übereinstimmung anschließend mit equals. Er ist kein eindeutiger Objektidentifikator.

Der Vertrag gilt in eine Richtung

Gleiche Objekte müssen denselben Hashcode zurückgeben. Auch ungleiche Objekte dürfen denselben Code haben; das ist eine Kollision. Eine Kollision macht sie nicht gleich, denn die Gleichheit wird weiterhin geprüft.

Ein Hashcode muss gleich bleiben, solange sich die von equals verwendeten Informationen nicht ändern. Bei einer korrekten Implementierung schließen unterschiedliche Hashcodes Gleichheit aus, aber gleiche Hashcodes belegen sie nicht.

Wenn eine Klasse equals überschreibt, aber Object.hashCode erbt, erhalten separate gleiche Objekte häufig unterschiedliche Codes. Eine Menge kann dann beide behalten und eine Map-Abfrage mit einem neu erzeugten gleichen Schlüssel kann fehlschlagen. Der genaue Fehler ist nicht garantiert, weil unterschiedliche Objekte kollidierende geerbte Codes haben können. Korrigiere den Vertrag, statt dich auf eine bestimmte fehlgeschlagene Suche zu verlassen.

Aus den gleichheitsrelevanten Feldern berechnen

Für die Buchklasse, die Titel und Jahr vergleicht:

@Override
public int hashCode() {
    return Objects.hash(title, year);
}

Importiere java.util.Objects. Dieselben Feldwerte erzeugen nun dasselbe Ergebnis. Eine Buchmenge speichert nach dem Hinzufügen zweier gleicher Bücher einen Eintrag und eine Map kann einen Wert mit einem neu erzeugten gleichen Buchschlüssel abrufen.

Objects.hash kombiniert die Hashcodes der Felder mithilfe von 31. Für „Dune“ ist der String-Hashcode 2141736. Die Kombination dieses Titels mit dem Jahr 1965 multipliziert 31 mit der Summe aus 31 und 2141736 und addiert dann 1965: Das Ergebnis ist 66396742.

Auch die Hashcode-Berechnung für Strings verwendet 31. Für „ab“ ergeben die Zeichencodes 97 und 98 den Wert 97 mal 31 plus 98, also 3105. Längere Berechnungen können den int-Bereich überschreiten und überlaufen. Ein negativer Hashcode ist daher normal.

Hashcode-RegelFolge für den Vertrag
Dieselben gleichheitsrelevanten Felder verwendenGleiche Objekte erhalten gleiche Codes
Ein Feld hinzufügen, das die Gleichheitsprüfung ignoriertGleiche Objekte können unterschiedliche Codes erhalten
Weniger gleichheitsrelevante Felder verwendenZulässig, kann aber mehr Kollisionen erzeugen
Immer 1 zurückgebenZulässig, führt aber zu schlechter Suchleistung
Den Schlüssel während der Speicherung nicht ändern

Angenommen, das Jahr eines Buchs ist veränderlich und beeinflusst seinen Hashcode. Nach dem Einfügen in eine Menge kann eine Änderung des Jahres dazu führen, dass contains(book) selbst mit derselben Referenz fehlschlägt: Die Suche verwendet nun einen anderen Code. Die Menge kann weiterhin die Größe 1 melden.

Halte gleichheitsrelevante Felder stabil, solange ein Objekt Element einer Menge oder Schlüssel einer Map ist. Müssen sie sich ändern, entferne das Objekt, solange seine alten Werte noch gelten, ändere es und füge es erneut ein. Ein Entfernungsversuch erst nach der Änderung kann auf dasselbe Suchproblem stoßen.

Auch generierte Gleichheitsprüfung braucht eine sinnvolle Datenauswahl

Java ab Version 16 unterstützt Records wie record Book(String title, int year) {}. Ein Record erzeugt die Zugriffsmethoden title() und year() sowie Gleichheitsprüfung, Hashcode-Berechnung und eine lesbare String-Darstellung aus allen seinen Komponenten. Die Komponentenfelder sind final. Dieses Beispiel ist ein sinnvoller unveränderlicher Wert, weil seine Komponenten ein String und eine ganze Zahl sind. Ein Record mit einer veränderlichen Liste würde dagegen weiterhin deren Zustand gemeinsam nutzen.

Für gewöhnliche Klassen kann eine IDE equals und hashCode gemeinsam erzeugen. Wähle die Felder aus, die die beabsichtigte Gleichheit definieren, und halte beide Methoden aufeinander abgestimmt, wenn sich diese Wahl ändert.

Wichtige Erkenntnisse

  • Gleiche Objekte benötigen gleiche Hashcodes; Kollisionen ungleicher Objekte sind erlaubt.
  • Überschreibe immer auch hashCode, wenn du equals überschreibst.
  • Berechne den Hashcode nur aus Daten, die zur Gleichheitsregel passen.
  • Halte Hashcode-relevante Felder stabil, solange das Objekt Element einer Menge ist oder als Map-Schlüssel dient.
  • Entferne einen Schlüssel vor seiner Änderung und füge ihn danach erneut ein.
  • Records erzeugen die Gleichheitsprüfung aus ihren Komponenten; veränderliche Komponenten erfordern weiterhin Sorgfalt.
5

Gib jeder Klasse eine klar abgegrenzte Aufgabe

Ein Textprogramm hat zwei Arten von Aufgaben: mit einem Benutzer kommunizieren sowie Daten und Regeln verwalten. Wenn diese Aufgaben in getrennten Klassen liegen, lassen sich die Regeln leichter prüfen und wiederverwenden. Verbinde die Klassen über Konstruktorparameter und kleine öffentliche Methoden. Ergänze eine Funktion zunächst dort, wo ihre Regel hingehört, überprüfe das Ergebnis mit bekannten Werten und gib der Oberfläche einen Befehl, der die Funktion aufruft und das Ergebnis anzeigt.

5.1 Eingabe, Ausgabe und Programmregeln

Die Benutzeroberfläche liest Eingaben und zeigt Ausgaben an. Die Programmlogik speichert Daten und wendet Regeln an. Bei einem getrennten Entwurf werden Werte an die Logik übergeben und Ergebnisse zurückgegeben; die Logikklassen verwenden weder Scanner noch System.out.

Die Objekte in main verbinden
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    TemperatureLog log = new TemperatureLog();
    UserInterface ui = new UserInterface(scanner, log);
    ui.start();
}

Mit import java.util.Scanner; erzeugt diese Objektverdrahtung einen Eingabeleser, ein Protokoll und eine Oberfläche. Sie gehört in die Einstiegsklasse des Programms. UserInterface speichert die übergebenen Objekte in Feldern, sodass jeder Befehl mit demselben Protokoll arbeitet.

AufgabeZuständige Klasse
Befehlstext lesenBenutzeroberfläche
Einen Messwert speichernTemperaturprotokoll
Mittelwert oder Maximum berechnenTemperaturprotokoll
Ein Ergebnis runden und ausgebenBenutzeroberfläche
Eine unmögliche Temperatur ablehnenTemperaturprotokoll
Ungültige Eingaben dem Benutzer erklärenBenutzeroberfläche
Werte zurückgeben, die jeder aufrufende Code verwenden kann

Ein Protokoll könnte add(double celsius), count(), average() und max() anbieten. Die Messwerte 18,5, 21,0 und 19,5 °C ergeben eine Summe von 59,0, eine Anzahl von 3, ein Maximum von 21,0 und einen Mittelwert von etwa 19,6667 °C. Die Benutzeroberfläche kann den zurückgegebenen Mittelwert mit Math.round(average * 10) / 10.0 runden und 19,7 °C anzeigen.

Die Rückgabe der Zahl erleichtert direkte Prüfungen. Eine Methode namens printAverage(), die nur ausgibt, koppelt eine Berechnung an die Konsolenausgabe, was die Wiederverwendung umständlicher macht.

Angenommen, für eine einfache Befehlsschleife nimmt add gültige Temperaturen an und count gibt ihre Anzahl zurück:

class UserInterface {
    private Scanner scanner;
    private TemperatureLog log;

    UserInterface(Scanner scanner, TemperatureLog log) {
        this.scanner = scanner;
        this.log = log;
    }

    void start() {
        while (true) {
            String command = scanner.nextLine();
            if ("quit".equals(command)) {
                break;
            }
            if ("count".equals(command)) {
                System.out.println(log.count());
            }
        }
    }
}

Die Schleife behandelt die Interaktion. Sie fragt das Protokoll nach einem Ergebnis und gibt es aus, ohne zu wissen, wie die Messwerte gespeichert werden. Ein Befehl für den Mittelwert sollte ein leeres Protokoll entsprechend dem dokumentierten Ergebnis der Logik behandeln oder count() prüfen, bevor er eine Methode aufruft, die Messwerte voraussetzt.

An der richtigen Stelle validieren

Die Logik kann add bei einem Messwert unter −273,15 °C false zurückgeben lassen, ohne den Wert zu speichern. Jeder aufrufende Code erhält dann dieselbe Datenregel. Die Benutzeroberfläche entscheidet, wie sie die Ablehnung meldet und erneut fragt. Auch eine hilfreiche Fehlermeldung für eine nicht numerische Eingabe gehört zur Oberfläche.

Verwende einen Scanner für System.in und übergib ihn an die lesenden Klassen. Mehrere Scanner können um gepufferte Eingaben konkurrieren und das Schließen eines Scanners schließt den zugrunde liegenden Eingabestrom. Die Lebensdauer des gemeinsam genutzten Scanners sollte der Code verwalten, der für die Programmeingabe verantwortlich ist.

Die Logik kann später über dieselben öffentlichen Methoden eine andere Oberfläche unterstützen. Die Wiederverwendung gelingt, weil Berechnungen und Gültigkeitsregeln unabhängig von einer bestimmten Befehlsschleife als Methodenergebnisse verfügbar sind.

Wichtige Erkenntnisse

  • Die Benutzeroberfläche liest und zeigt an; die Logik speichert Daten, setzt Regeln durch und gibt Ergebnisse zurück.
  • main erzeugt die gemeinsam genutzten Objekte und startet die Oberfläche.
  • Übergib einen Scanner und das Logikobjekt an den Konstruktor der Benutzeroberfläche.
  • Datenregeln gehören in die Logik; Eingabemeldungen und Ausgabeformatierung gehören in die Benutzeroberfläche.
  • Die Rückgabe von Ergebnissen erleichtert direkte Prüfungen und die Wiederverwendung mit einer anderen Oberfläche.

5.2 Eine Funktion ergänzen und ihr Verhalten prüfen

Ein Programm mit getrennter Oberfläche und Logik kann in kleinen Schritten wachsen. Gib der Logik eine nützliche Methode, prüfe sie direkt und verbinde sie dann mit einem Befehl, der ihre Eingabe erfasst und ihr Ergebnis anzeigt.

Eine Abfrage zur Logik hinzufügen

Ein TemperatureLog, das eine initialisierte ArrayList<Double> readings speichert, kann Messwerte oberhalb eines Schwellenwerts zählen:

int countAbove(double limit) {
    int count = 0;
    for (double reading : readings) {
        if (reading > limit) {
            count++;
        }
    }
    return count;
}

Der strenge Vergleich schließt Messwerte aus, die genau dem Schwellenwert entsprechen. Für 18,5, 21,0, 19,5 und 23,0 °C gibt countAbove(20.0) den Wert 2 zurück. Platziere den Vergleich im Protokoll, damit für jeden aufrufenden Code dieselbe Regel gilt.

Der Befehl der Benutzeroberfläche liest einen Schwellenwert, ruft log.countAbove(limit) auf und gibt die Anzahl aus. Werden einzelne Befehle lang, zerlege start() in private Hilfsmethoden wie addReading() und showStatistics(). Die Hilfsmethoden organisieren die Interaktion; sie verlagern die Datenregeln nicht aus dem Protokoll heraus.

Ohne Eingabe von Befehlen prüfen

Erzeuge das Logikobjekt und rufe seine Methoden mit Werten auf, deren Ergebnisse du kennst:

TemperatureLog log = new TemperatureLog();
log.add(18.5);
log.add(21.0);
log.add(19.5);
log.add(23.0);
System.out.println(log.count() == 4);
System.out.println(log.average() == 20.5);
System.out.println(log.max() == 23.0);
System.out.println(log.countAbove(20.0) == 2);

Die Summe ist 82,0, der erwartete Mittelwert also 20,5. Gerade diese Werte haben exakte Binärdarstellungen, sodass sich die direkten Gleichheitsprüfungen für das Beispiel eignen. Prüfe außerdem ein leeres Protokoll entsprechend seinem dokumentierten Verhalten, einen Schwellenwert, der einem Messwert entspricht, und die Ablehnung ungültiger Eingaben ohne Änderung der Anzahl. Automatisierte Testwerkzeuge bauen auf derselben Idee einer bekannten Eingabe und eines erwarteten Ergebnisses auf.

Die Speicherung nur ändern, wenn sie die Abfragen weiterhin unterstützt

Eine Oberfläche, die nur average, count und max aufruft, muss nicht wissen, ob das Protokoll eine Liste enthält. Felder für laufende Summe, Anzahl und Maximum können diese drei Abfragen beantworten, ohne jeden Messwert zu behalten.

Eine Abfrage countAbove(limit) mit beliebigem Schwellenwert benötigt mehr Informationen. Die Stichproben 18, 22, 23 und 20, 20, 23 haben dieselbe Summe 63, dieselbe Anzahl 3 und dasselbe Maximum 23, aber ihre Anzahlen oberhalb von 20 betragen 2 und 1. Werden nur diese drei Aggregate gespeichert, geht der Unterschied verloren. Die öffentlichen Methoden beschreiben, was nach einer Änderung der Speicherung weiterhin funktionieren muss.

Die Entwurfsideen verbinden

Ein Rezeptbuch kann eine private Liste von Rezepten verwalten. Jedes Rezept kann Zutaten mit Mengen in Gramm und eine Kochzeit in Minuten enthalten. Die Gleichheitsprüfung kann Duplikatprüfungen unterstützen, während eine Benutzeroberfläche Rezepte über Methoden des Buchs hinzufügt, auflistet und sucht. Eine kopierte Ergebnisliste kann ihre Zusammensetzung schützen und trotzdem Rezeptobjekte gemeinsam nutzen; entscheide, ob der aufrufende Code diese Rezepte ändern darf.

Arbeite in kleinen Schritten: Implementiere eine Methode, prüfe sie und ergänze dann ihren Befehl. Wenn eine Änderung ein vorhandenes Ergebnis beeinträchtigt, kehre zu einem funktionierenden Zustand zurück und isoliere diese Änderung, bevor du eine weitere Funktion hinzufügst.

Wichtige Erkenntnisse

  • Ergänze und prüfe die Logikmethode, bevor du ihren Befehl mit der Benutzeroberfläche verbindest.
  • Verwende bekannte Eingaben und erwartete Ergebnisse, um die Logik direkt zu prüfen.
  • Beziehe Grenzwertfälle wie eine leere Collection oder einen exakt erreichten Schwellenwert ein.
  • Eine Änderung der Speicherung muss genügend Informationen für jede öffentliche Abfrage erhalten.

Objektreferenzen in Java: Beispiele

5 Interaktive Lektionen

1

Verfolge Variablen, ohne die Objekte aus dem Blick zu verlieren

15 Schritte
Verfolge Kopien primitiver Werte, gemeinsam genutzte Referenzen, Neuzuweisungen und null in einem kurzen Programm.
  1. 1
    EinführungVariablen und Objekte zeichnen

    Eine Variable kann eine Zahl direkt oder eine Referenz enthalten, die zu einem Objekt führt. Dieser Unterschied erklärt, warum das Kopieren einer Variablen manchmal unabhängige Daten liefert und manchmal eine Änderung gemeinsam sichtbar macht.

    Wir zeichnen Variablen getrennt von Objekten und verfolgen dann jede Anweisung. Unterscheide die Anzahl der Pfeile von der Anzahl der Objekte: Mehrere Pfeile können bei einem Objekt enden.

  2. 2
    ErklärungEin Wert kann ein Datum oder eine Referenz sein

    Java hat acht primitive Datentypen: byte, short, int, long, float, double, char und boolean. Ihre Variablen enthalten Daten direkt. Klassen und Arrays sind Referenztypen; ihre Variablen enthalten Referenzen oder null.

    In int count = 13; ist der Wert 13. In Tally t = new Tally(13); ist der Wert in t eine Referenz auf ein Objekt, das seine eigenen Daten enthält.

  3. 3
    BeispielEinen primitiven Wert kopieren

    Verfolge die Zuweisungen der Reihe nach:

    int start = 13;
    int next = start;
    next += 2;
    

    Die zweite Anweisung kopiert 13 nach next. Die dritte liest diese Kopie und ersetzt sie durch 15. start bleibt 13, weil keine Anweisung der Variablen einen neuen Wert zuweist. Zeichne zwei Kästchen mit separaten Zahlen; es gibt kein gemeinsam genutztes Objekt.

  4. 4
    BeispielEinen Referenzwert kopieren

    Angenommen, Tally speichert eine ganze Zahl, addOne() erhöht sie und value() liest sie.

    Tally first = new Tally(13);
    Tally next = first;
    next.addOne();
    

    Ein new erzeugt einen Zähler. Die Zuweisung gibt next einen weiteren Weg dorthin. Der Aufruf von addOne() ändert den gespeicherten Wert auf 14, den nun auch first.value() liest. Die zweite Variable ist kein zweiter Zähler.

  5. 5
    HerausforderungEin Objekt von seinem Alias trennen

    Was gibt a.value() bei denselben Tally-Methoden zurück?

    Tally a = new Tally(20);
    Tally b = new Tally(a.value());
    b.addOne();
    

    ---reveal---

    Der Aufruf gibt 20 zurück. Das zweite new erzeugt einen unabhängigen Zähler, der mit 20 initialisiert wird; das Inkrement ändert nur dieses zweite Objekt auf 21. Einen Feldwert zu lesen und in einem Konstruktor zu verwenden, unterscheidet sich vom Kopieren der ursprünglichen Objektreferenz.

  6. 6
    VerbindungManche gemeinsam genutzten Objekte sind unveränderlich

    Strings und Wrapper wie Integer sind Referenztypen. Autoboxing wandelt primitive Werte für Anwendungen wie ArrayList<Integer> in Wrapper um.

    Strings sind unveränderlich: Wenn left und right „cedar“ gemeinsam nutzen, ändert die Zuweisung right = right.toUpperCase() die ursprünglichen Zeichen nicht. left bleibt „cedar“. Das zurückgegebene Ergebnis in Großbuchstaben wird nur right zugewiesen.

  7. 7
    AnalogieÄnderungen mit Pfeilen verfolgen

    Stell dir jede Variable als Karte vor, die einen Weg zu einem Objekt zeigt. Das Kopieren der Karte ergibt einen weiteren Weg zum selben Ziel. Schreibst du auf eine Karte einen anderen Weg, werden die anderen dadurch nicht umgeschrieben.

    Zeichne in einem Speicherdiagramm von jeder Referenzvariablen einen Pfeil zu ihrem Objekt. Ein Setter ändert die Feldwerte des Objekts; eine Zuweisung ändert den Pfeil einer Variablen. Halte diese beiden Änderungen im Bild klar auseinander.

  8. 8
    BeispielZuerst gemeinsam genutzten Zustand ändern

    Die Klasse Crate speichere eine Masse in Kilogramm und habe die Methoden setMass und mass.

    Crate a = new Crate(3);
    Crate b = a;
    b.setMass(6);
    

    Zeichne a und b so, dass beide auf eine Kiste zeigen. Der Setter ändert die Masse in dieser Kiste von 3 auf 6. Sowohl a.mass() als auch b.mass() geben nun 6 zurück; der Setter hat keine der beiden Variablen umgelenkt.

  9. 9
    BeispielDann eine Variable umlenken

    Setze die Ablaufverfolgung der Kiste fort:

    b = new Crate(9);
    

    Erzeuge ein zweites Objekt mit der Masse 9 und lenke nur b um. Die Variable a zeigt weiterhin auf die ursprüngliche Kiste, deren Masse bei 6 bleibt. Nun sind zwei Kistenobjekte erreichbar. Dies ist eine Neuzuweisung, daher wird die gespeicherte Masse der ersten Kiste nicht zurückgesetzt.

  10. 10
    HerausforderungDen verbleibenden Weg erhalten

    Fahre mit Crate c = a; a = null; fort. Über welche Variable ist die Kiste mit 6 kg noch erreichbar und kann sie von der Speicherbereinigung entfernt werden?

    ---reveal---

    Sie ist über c weiterhin erreichbar und kann daher nicht von der Speicherbereinigung entfernt werden. Die frühere Zuweisung hat die Referenz nach c kopiert; das Setzen von a auf null hat nur einen Weg entfernt. Die separate Kiste mit 9 kg bleibt über b erreichbar.

  11. 11
    KernaussageEin Feld oder eine Liste kann einen Weg erhalten

    Wenn der letzte nutzbare Weg zu einem Objekt verschwindet, wird es unerreichbar und kann von der automatischen Speicherbereinigung entfernt werden. Java wählt den Zeitpunkt der Bereinigung; der Verlust einer Referenz verspricht keine sofortige Freigabe.

    Ein Weg kann über ein anderes Objekt oder eine Liste führen. Prüfe neben lokalen Variablen auch diese Verbindungen, bevor du ein Objekt als unerreichbar einstufst. Ein Listeneintrag kann ein Objekt erhalten, nachdem seine ursprüngliche Variable ihren Gültigkeitsbereich verlassen hat.

  12. 12
    ErklärungNull bedeutet, dass das Ziel fehlt

    null ist kein leeres Objekt. Ein String mit dem Wert null hat kein Objekt, auf dem length() aufgerufen werden kann, während "" ein verwendbarer String der Länge null ist. Referenzfelder haben standardmäßig den Wert null; lokalen Variablen muss vor dem Lesen ein Wert zugewiesen werden.

    Auch Wrapper können null sein. Integer n = null; int count = n; löst eine NullPointerException aus, wenn das Unboxing versucht, einen primitiven Wert aus dem fehlenden Wrapper zu erhalten.

  13. 13
    TippDen Fehler am fehlgeschlagenen Aufruf lesen

    Suche bei einem Nullfehler die Zeilennummer und dann den Instanzaufruf, den Feldzugriff oder das Unboxing in dieser Zeile. Hilfreiche Meldungen, die seit Java 15 standardmäßig aktiviert sind, können das fehlende Feld benennen.

    Verfolge den Ablauf zurück bis zu dem Konstruktor, Suchergebnis oder der Zuweisung, die ein Objekt hätte liefern sollen. „null“ in einem ausgegebenen Text ist ein Hinweis: Die Ausgabe eines Strings mit dem Wert null kann gelingen, obwohl ein späterer Instanzaufruf fehlschlägt.

  14. 14
    HerausforderungDen gesamten Ablauf absichern

    Eine Suche weist mit Crate found = findCrate(); entweder eine Kiste oder null zu. Welche Bedingung prüft sicher, ob die Masse über 5 kg liegt? Warum müssen ihre Prüfungen in dieser Reihenfolge stehen?

    ---reveal---

    Verwende found != null && found.mass() > 5. Die Prüfung auf null wird zuerst ausgeführt und && überspringt mass(), wenn keine Kiste vorhanden ist. Stünde der Methodenaufruf zuerst, würde null dereferenziert, bevor die Prüfung erreicht wird. Für eine zurückgegebene Referenz gelten dieselben Regeln wie für die zuvor verfolgten Aliasse.

  15. 15
    ZusammenfassungEinen Referenzablauf prüfen

    Markiere bei einer neuen Ablaufverfolgung jedes new, kopiere bei jeder Zuweisung den Wert und ändere bei einer Zustandsänderung nur das betroffene Feld. Verfolge dann alle verbleibenden Wege, bevor du ein Objekt als unerreichbar einstufst.

    Bestimme an einer fehlschlagenden Zeile genau die Operation, die ein Objekt benötigt. Prüfe vor dieser Operation auf null oder initialisiere das Feld mit dem Objekt, das das Programm tatsächlich braucht, etwa einer leeren Liste.

2

Verfolge ein Objekt beim Übergeben an eine Methode und bei der Rückgabe

12 Schritte
Verfolge Zustandsänderung und Neuzuweisung eines Parameters und wähle dann zwischen gemeinsam genutzten und unabhängigen Rückgabewerten.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungEinen Aufruf und sein Ergebnis verfolgen
  2. 2
    ErklärungEin Parameter erhält seinen eigenen Wert
  3. 3
    BeispielDas gemeinsam genutzte Objekt ändern
  4. 4
    BeispielDen lokalen Weg neu zuweisen
  5. 5
    HerausforderungKann eine Methode Variablen des aufrufenden Codes vertauschen?
  6. 6
    VerbindungDas Empfängerobjekt mit seinem Argument vergleichen
  7. 7
    ErklärungDer Rückgabetyp verspricht keine Kopie
  8. 8
    BeispielEin unabhängiges Einstellrad zur Rückgabe erzeugen
  9. 9
    HerausforderungEinen Eintrag finden, der aktualisiert werden kann
  10. 10
    TippEin Getter kann einen verborgenen Alias offenlegen
  11. 11
    HerausforderungEine Rückgabe mit einem Parameteraufruf verbinden
  12. 12
    ZusammenfassungEine Methode an beiden Übergabestellen untersuchen
3

Erstelle und schütze eine Collection von Objekten

16 Schritte
Verbinde Referenzfelder und Listen, suche gespeicherte Einträge und unterscheide Kopien der Collection von Kopien ihrer Elemente.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungDie Zustandsebenen trennen
  2. 2
    ErklärungEine Hat-ein-Beziehung modellieren
  3. 3
    BeispielDie Datumsregel delegieren
  4. 4
    HerausforderungDas übergebene Datum schützen
  5. 5
    ErklärungEin Eintrag ist ein weiterer Weg
  6. 6
    BeispielEine Summe mit einer Schleife berechnen
  7. 7
    BeispielDas ausgewählte Objekt behalten
  8. 8
    HerausforderungEin fehlendes Ergebnis zum richtigen Zeitpunkt zurückgeben
  9. 9
    TippEntfernen und Traversierung trennen
  10. 10
    ErklärungDem Besitzerobjekt eine initialisierte Liste geben
  11. 11
    BeispielNützliche Operationen anbieten
  12. 12
    HerausforderungEine Listenkopie von ihren Elementen unterscheiden
  13. 13
    VerbindungBeide Richtungen schützen
  14. 14
    BeispielIn ein neues Ergebnis filtern
  15. 15
    HerausforderungEine Kopie durch ein anderes Objekt verfolgen
  16. 16
    ZusammenfassungDie Übergabestellen der Collection prüfen
4

Entwickle eine Gleichheitsprüfung, auf die Collections vertrauen können

16 Schritte
Wähle zwischen Identitäts- und Inhaltsvergleich, implementiere equals und hashCode gemeinsam und untersuche stabile Schlüssel.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungFestlegen, was eine Übereinstimmung bedeutet
  2. 2
    BeispielGleicher Text in separaten Objekten
  3. 3
    TippDie Regel nicht durch Zwischenspeicherung bestimmen lassen
  4. 4
    HerausforderungEine Klasse anhand des Inhalts vergleichen
  5. 5
    ErklärungFelder und Signatur wählen
  6. 6
    BeispielIdentität akzeptieren und falsche Typen ablehnen
  7. 7
    BeispielDie ausgewählten Felder vergleichen
  8. 8
    HerausforderungEine Vertragsverletzung erkennen
  9. 9
    KernaussageMehr als ein passendes Paar prüfen
  10. 10
    ErklärungHashcodes grenzen die möglichen Treffer ein
  11. 11
    BeispielDen passenden Hashcode erzeugen
  12. 12
    HerausforderungKollision und Gleichheit auseinanderhalten
  13. 13
    BeispielDie Wirkung eines geänderten Schlüssels erkennen
  14. 14
    VerbindungEin einfacher Record liefert beide Methoden
  15. 15
    HerausforderungDie Menge mit generierter Gleichheitsprüfung verfolgen
  16. 16
    ZusammenfassungEine Werteklasse überprüfen
5

Ergänze eine Funktion, ohne Eingaben und Regeln zu vermischen

11 Schritte
Verbinde eine Konsolenoberfläche mit wiederverwendbarer Logik, prüfe eine neue Abfrage direkt und erhalte die dafür nötigen Daten.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungRegeln mit einem Dialog verbinden
  2. 2
    ErklärungDie beiden Aufgaben benennen
  3. 3
    BeispielGemeinsam genutzte Objekte verbinden
  4. 4
    BeispielDas Ergebnis zur Anzeige zurückgeben
  5. 5
    HerausforderungEine Ablehnung in der richtigen Klasse platzieren
  6. 6
    ErklärungDer neuen Abfrage eine Logikmethode geben
  7. 7
    BeispielMit bekannten Eingaben prüfen
  8. 8
    HerausforderungDen Vorschlag zur Speicherung beurteilen
  9. 9
    TippDas Programm in geprüften Schritten erweitern
  10. 10
    HerausforderungZu einer anderen Oberfläche wechseln
  11. 11
    ZusammenfassungEine neue Funktion an ihrer Übergabestelle prüfen

Objektreferenzen in Java: Übungsaufgaben

40 Übungsfragen in 4 Sets

Set 1: Verfolge Werte, Aufrufe und einfache Vergleiche10 Übungsfragen

Frage 1 von 10

Was enthält original nach diesem Code?

int original = 8;
int duplicate = original;
duplicate = 11;
  1. 11
  2. 8
  3. 19
  4. 0
Antwort zeigenAntwort ausblenden
Antwort: B
Die erste Zuweisung speichert 8 in jeder der beiden primitiven Variablen. Die spätere Zuweisung ändert nur duplicate, sodass original weiterhin 8 enthält. Würde man die Variablen als Aliasse behandeln, ergäbe sich 11. Eine int-Zuweisung kopiert aber die Zahl selbst und erzeugt kein gemeinsam genutztes Objekt.

Frage 2 von 10

Lamp hat ein boolesches Feld, das anfangs false ist; turnOn() setzt es auf true und isOn() liest es. Was geben a.isOn() und b.isOn() in dieser Reihenfolge zurück?

Lamp a = new Lamp();
Lamp b = a;
b.turnOn();
b = new Lamp();
  1. false, false
  2. true, true
  3. false, true
  4. true, false
Antwort zeigenAntwort ausblenden
Antwort: D
Der Aufruf schaltet die Lampe ein, die von a und b gemeinsam genutzt wird. Die Neuzuweisung lenkt b anschließend auf eine neue Lampe, deren Feld mit false beginnt, während a auf die eingeschaltete Lampe verweist. Wer zweimal true erwartet, übersieht das durch die letzte Anweisung erzeugte neue Objekt.

Frage 3 von 10

label kann null sein. Welche Bedingung prüft sicher, ob der String mindestens drei Zeichen hat?
  1. label != null && label.length() >= 3
  2. label.length() >= 3 && label != null
  3. label != null & label.length() >= 3
  4. label.equals(null) && label.length() >= 3
Antwort zeigenAntwort ausblenden
Antwort: A
Die Prüfung auf null wird zuerst ausgeführt und && überspringt den Längenaufruf, wenn die Prüfung falsch ist. Stünde der Längenaufruf zuerst, würde er bereits das fehlende Objekt dereferenzieren. Ein einzelnes & wertet ebenfalls beide Seiten aus und bietet daher nicht den nötigen Schutz durch Kurzschlussauswertung.

Frage 4 von 10

Eine Methode erhält ein Robot r ungleich null. Sie führt nur r = new Robot(); aus. Was geschieht mit der Variablen des aufrufenden Codes, die als Argument übergeben wurde?
  1. Sie verweist nun auf den neuen Roboter.
  2. Sie wird bei der Rückkehr der Methode null.
  3. Sie verweist weiterhin auf den ursprünglichen Roboter.
  4. Sie verweist auf eine Kopie des ursprünglichen Roboters.
Antwort zeigenAntwort ausblenden
Antwort: C
Der Parameter erhält eine Kopie der Referenz des aufrufenden Codes. Die Zuweisung einer neuen Referenz an diesen Parameter ändert nur die lokale Variable der Methode. Um die Referenz des aufrufenden Codes zu ersetzen, wäre dort eine Zuweisung nötig; diese Methode ändert auch den Zustand des ursprünglichen Roboters nicht.

Frage 5 von 10

Gauge.copy() gibt new Gauge(value) zurück. Ein Messgerät beginnt bei 6 und raise() addiert 1. Welche Werte zeigen first und second an?

Gauge first = new Gauge(6);
Gauge second = first.copy();
second.raise();
  1. 7 and 7
  2. 6 and 7
  3. 6 and 6
  4. 7 and 6
Antwort zeigenAntwort ausblenden
Antwort: B
Die Kopiermethode erzeugt ein zweites Messgerät, das mit dem Wert des ersten initialisiert wird. Das Erhöhen des neuen Objekts ändert seinen Wert auf 7 und lässt das erste bei 6. Zweimal 7 ergäbe sich bei gemeinsamer Nutzung der ursprünglichen Referenz, aber die Methode erzeugt ausdrücklich ein separates Objekt.

Frage 6 von 10

Ein Device-Konstruktor speichert sein veränderliches Settings-Argument direkt. Sein Getter gibt eine Kopie der gespeicherten Einstellungen zurück. Zwei Geräte erhalten dasselbe Einstellungsobjekt und der aufrufende Code ändert dieses ursprüngliche Objekt anschließend. Welche Entwurfsänderung macht jedes Gerät von dieser externen Änderung und vom anderen Gerät unabhängig?
  1. Einstellungen direkt speichern; den Getter das gespeicherte Objekt zurückgeben lassen.
  2. Beide Konstruktoren beibehalten; Einstellungen nur beim Getteraufruf kopieren.
  3. Einstellungen in jedem Konstruktor kopieren; den kopierenden Getter beibehalten.
  4. Eine gemeinsame Einstellungskopie erzeugen; sie an beide Konstruktoren übergeben.
Antwort zeigenAntwort ausblenden
Antwort: C
Beide Geräte nutzen derzeit das übergebene Objekt gemeinsam, sodass der ursprünglich aufrufende Code ihren Zustand ohne Getter ändern kann. Jeder Konstruktor muss diesen übergebenen Zustand kopieren, damit jedes Gerät ein eigenes Objekt erhält. Ein kopierender Getter schützt den Zugriff nach außen, kann aber einen bei der Konstruktion erhaltenen Alias nicht entfernen.

Frage 7 von 10

Ein Sensor s ungleich null wird zu zwei Listen hinzugefügt. Eine Liste entfernt ihren Eintrag für s. Was geschieht mit dem Sensor in der anderen Liste?
  1. Er bleibt dasselbe Sensorobjekt.
  2. Er wird aus der anderen Liste entfernt.
  3. Er wird durch einen kopierten Sensor ersetzt.
  4. Er wird zu einem Listenelement mit null.
Antwort zeigenAntwort ausblenden
Antwort: A
Das Entfernen eines Eintrags ändert die Zugehörigkeit zu dieser einen Liste. Die andere Liste enthält weiterhin ihre eigene Referenz auf den Sensor und behält somit dasselbe Objekt. Das Entfernen zerstört den Sensor nicht und durchsucht keine anderen Collections nach Referenzen darauf.

Frage 8 von 10

Was geben diese beiden Ausdrücke zurück?

String word = new String("maple");
word == "maple"
word.equals("maple")
  1. true, true
  2. false, false
  3. true, false
  4. false, true
Antwort zeigenAntwort ausblenden
Antwort: D
Die explizite Konstruktion erzeugt ein anderes String-Objekt als das Literal, daher ist die Identitätsprüfung false. String.equals vergleicht die Zeichen und gibt true zurück. Gleicher Text macht separat erzeugte Strings nicht identisch, auch wenn gleiche Literale ein Objekt gemeinsam nutzen können.

Frage 9 von 10

Welche Deklaration überschreibt die von Object geerbte Gleichheitsmethode?
  1. public boolean equals(Note other)
  2. public int equals(Object other)
  3. public boolean equals(Object other)
  4. public boolean equal(Object other)
Antwort zeigenAntwort ausblenden
Antwort: C
Die geerbte Methode hat den Namen equals, den Rückgabetyp boolean und den Parametertyp Object. Eine passende Deklaration überschreibt sie; mit @Override kann der Compiler die Übereinstimmung prüfen. Ein Parameter vom Typ Note erzeugt eine Überladung und lässt die geerbte Methode bestehen.

Frage 10 von 10

Eine Konsolenanwendung erfasst Lesezeiten. Dieselbe Logik soll später eine Webseite unterstützen. Wohin gehören die Berechnung der gesamten Sekunden und die Ausgabe von „Gesamt“?
  1. Berechnung in die Logik; Ausgabe in die Konsolenoberfläche.
  2. Berechnung und Ausgabe in die Konsolenoberfläche.
  3. Berechnung und Ausgabe in die Logikklasse.
  4. Ausgabe in die Logik; Berechnung in die Konsolenoberfläche.
Antwort zeigenAntwort ausblenden
Antwort: A
Die Gesamtdauer ist eine Datenberechnung, die beide Oberflächen benötigen. Daher sollte die Logik sie zurückgeben. Die Konsolenoberfläche wählt ihre Beschriftung und gibt das Ergebnis aus. Läge die Berechnung in der Konsolenoberfläche, müsste die spätere Weboberfläche sie duplizieren oder von konsolenspezifischem Code abhängen.
Set 2: Wende gemeinsame Nutzung, Collections und Programmgrenzen an10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Was geschieht nach der letzten Zeile mit a?

String a = "river";
String b = a;
b = b.toUpperCase();
  1. Es verweist nun auf den Text „RIVER“.
  2. Es wird nach der Zuweisung null.
  3. Es verweist weiterhin auf den Text „river“.
  4. Es verweist nun auf einen leeren String.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Was geschieht, wenn dieser Code ausgeführt wird?

Integer reading = null;
int value = reading;
  1. Unboxing löst eine NullPointerException aus.
  2. value erhält den primitiven Wert 0.
  3. reading wird zu einem Ganzzahl-Wrapper für 0.
  4. Die Kompilierung schlägt fehl, weil Wrapper kein null enthalten können.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Meter speichert eine ganze Zahl und hat set(int) und get(). Was gibt m.get() nach dem Aufruf zurück?

static void adjust(Meter x) {
    x.set(14);
    x = new Meter(22);
    x.set(25);
}
Meter m = new Meter(9);
adjust(m);
  1. 9
  2. 22
  3. 25
  4. 14
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Bin.items() gibt seine private Liste veränderlicher Part-Objekte direkt zurück. Aufrufender Code soll die zurückgegebene Liste und ihre Teile untersuchen und ändern dürfen, ohne die Zusammensetzung des Behälters oder den Zustand der gespeicherten Teile zu ändern. Was muss das Ergebnis enthalten?
  1. Eine neue Liste mit den gespeicherten Teilreferenzen.
  2. Eine neue Liste mit unabhängigen Kopien der Teile.
  3. Die gespeicherte Liste, nachdem jedes Teil darin kopiert wurde.
  4. Die gespeicherte Liste mit unveränderlichen numerischen Indizes.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Namenssuche prüft eine Liste der Reihe nach. Bei den Namen „Iris“, „Noel“, „Sana“ gibt sie bei der Suche nach „Sana“ null zurück. Derzeit steht return null in einem else nach der Namensprüfung innerhalb der Schleife. Wohin gehört diese Rückgabe?
  1. Vor den Beginn der Prüfung aller Einträge durch die Schleife.
  2. Hinter die Schleife, wenn sie keinen Treffer gefunden hat.
  3. Unmittelbar hinter die erste erfolgreiche Namensprüfung.
  4. Innerhalb der Schleife vor dem Lesen jedes neuen Namens.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

copy wird mit new ArrayList<>(original) erzeugt. Beide Listen enthalten anfangs dasselbe veränderliche Clip c. Der aufrufende Code ändert c über copy.get(0) und leert dann copy. Was bleibt in original?
  1. Ein Eintrag für den geänderten Clip.
  2. Ein Eintrag für den unveränderten Clip.
  3. Keine Einträge; beide Listen sind leer.
  4. Ein null-Eintrag anstelle des Clips.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Badge hat Felder und einen Konstruktor, überschreibt aber equals nicht. Was ist das Ergebnis?

new Badge("Q7").equals(new Badge("Q7"))
  1. true, weil die Feldwerte übereinstimmen.
  2. true, weil Konstruktoren Gleichheit definieren.
  3. false, weil Strings keine gleichheitsrelevanten Felder sein können.
  4. false, weil es separate Objekte sind.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Tag.equals vergleicht nur code. Tag.hashCode verwendet sowohl code als auch das veränderliche color. Zwei Tags haben denselben Code, aber unterschiedliche Farben. Ein Entwickler schlägt vor, zur Korrektur einen konstanten Hashcode zurückzugeben. Wie sind die beiden Regeln zu beurteilen?
  1. Die aktuelle Regel kann den Vertrag verletzen; eine Konstante ist zulässig, aber ineffizient.
  2. Die aktuelle Regel ist gültig; eine Konstante verletzt die Hashcode-Berechnung für verschiedene Objekte.
  3. Beide Regeln verletzen den Vertrag, weil Hashcodes eindeutig sein müssen.
  4. Beide Regeln sind gleich effizient, weil die Gleichheitsprüfung Treffer bestätigt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Programm zur Zeiterfassung lehnt negative Zeitdauern ab. Wo sollten die Ablehnung und die Meldung, die den Benutzer zu einem erneuten Versuch auffordert, implementiert werden?
  1. Ablehnung in der Oberfläche; Meldung in der Logik.
  2. Ablehnung und Meldung nur in der Oberfläche.
  3. Ablehnung in der Logik; Meldung in der Oberfläche.
  4. Ablehnung und Meldung nur in der Logik.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein Protokoll bietet derzeit count(), average() und max() an. Sein Entwickler ergänzt countAbove(limit) für einen beliebigen vom aufrufenden Code übergebenen Schwellenwert und ersetzt dann alle Messwerte durch nur eine Summe, eine Anzahl und ein Maximum. Warum lässt sich die neue Abfrage nicht mehr für jedes Protokoll korrekt implementieren?
  1. Der Mittelwert wird falsch, sobald die Messwerte einen Schwellenwert enthalten.
  2. Verschiedene Messwertlisten können gleiche Aggregate, aber unterschiedliche Anzahlen oberhalb eines Schwellenwerts haben.
  3. Ein Maximum lässt sich nur berechnen, wenn jedes ursprüngliche Messwertobjekt erhalten bleibt.
  4. Eine Änderung eines privaten Felds verlangt immer auch Änderungen an jedem Oberflächenbefehl.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 3: Behebe Fehler bei Aliasen, Gleichheit und Collection-Entwürfen10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Es existieren nur die gezeigten Referenzen und alle Variablen bleiben in Gebrauch. Cell enthält veränderliche Wertdaten. Wie viele Zellenobjekte bleiben erreichbar?

Cell a = new Cell(3);
Cell b = new Cell(8);
Cell c = a;
a = b;
b = null;
c = a;
  1. Zwei Zellenobjekte.
  2. Drei Zellenobjekte.
  3. Keine Zellenobjekte.
  4. Ein Zellenobjekt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine Klasse deklariert private ArrayList<Note> notes;. Ihr Konstruktor weist notes nie einen Wert zu und add ruft sofort notes.add(note) auf. Welche Änderung macht eine neu konstruierte Instanz bereit, Notizen zu speichern?
  1. Eine lokale Liste in add deklarieren und das Feld unverändert lassen.
  2. notes im Konstruktor ausdrücklich null zuweisen.
  3. notes mit new ArrayList<>() initialisieren.
  4. notes öffentlich machen, damit aufrufender Code auf das Feld zugreifen kann.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Box hat ein Ganzzahlfeld, das von get() gelesen und von set(int) geändert wird. Welche Werte zeigen left.get() und right.get() nach diesem Aufruf an?

static void revise(Box x, Box y) {
    x.set(10);
    x = y;
    x.set(16);
}
Box left = new Box(4);
Box right = new Box(7);
revise(left, right);
  1. 10 and 16
  2. 16 and 16
  3. 4 and 16
  4. 10 and 7
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein Member speichert ein SimpleDate joined ungleich null und muss entscheiden, ob es vor einem anderen Mitglied beigetreten ist. Wo sollte der Vergleich von Jahr, Monat und Tag implementiert werden?
  1. In jedem Oberflächenbefehl, der Mitglieder vergleicht.
  2. In SimpleDate.before, aufgerufen von der Mitgliedsmethode.
  3. In Member.toString, vom aufrufenden Code als Text zurückgelesen.
  4. In jedem aufrufenden Code nach der öffentlichen Freigabe aller Datumsfelder.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Ein Queue-Konstruktor speichert eine übergebene ArrayList<Job> direkt, aber sein Getter gibt eine neue flache Liste zurück. Der aufrufende Code leert die ursprüngliche Eingabeliste. Die Warteschlange muss ihre ursprüngliche Zusammensetzung behalten, während Jobobjekte gemeinsam genutzt bleiben dürfen. Welche Korrektur erfüllt diese Anforderung?
  1. Die übergebene Liste auch im Konstruktor kopieren.
  2. Den Konstruktor beibehalten und jeden Job im Getter kopieren.
  3. Die interne Liste zurückgeben, damit beide Listen synchron bleiben.
  4. Den Getter so ändern, dass er jedes Mal eine neue leere Liste zurückgibt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Token deklariert public boolean equals(Token other) ohne @Override. Direkte Vergleiche scheinen korrekt, aber eine ArrayList<Token> findet ein neues Token mit übereinstimmenden Feldern nicht. Was ist das Signaturproblem?
  1. Die Methode überschreibt equals, aber Listen vergleichen nur Hashcodes.
  2. Die Methode kann keine privaten Felder eines anderen Tokenobjekts lesen.
  3. Die Methode überlädt equals und lässt equals(Object) unverändert.
  4. Die Methode muss eine ganze Zahl zurückgeben, damit Collection-Suchen funktionieren.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Key.equals und hashCode verwenden beide serial. Ein veränderlicher Schlüssel wird zu einem HashSet hinzugefügt, dann ändert sich seine Seriennummer und eine Suche schlägt fehl. Welche Reihenfolge ermöglicht eine geplante Änderung der Seriennummer korrekt und erhält dabei die Zugehörigkeit zur Menge?
  1. Seriennummer ändern, mit neuer Seriennummer entfernen und erneut hinzufügen.
  2. Seriennummer ändern, contains aufrufen und den Eintrag unverändert lassen.
  3. Schlüssel ein zweites Mal hinzufügen, Seriennummer ändern und einmal entfernen.
  4. Mit alter Seriennummer entfernen, Seriennummer ändern und erneut hinzufügen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine Methode der Benutzeroberfläche benötigt die Anzahl gespeicherter Aufgaben. Was sollte die Logikmethode normalerweise tun?
  1. Die Anzahl auf der Konsole ausgeben.
  2. Die Anzahl als ganze Zahl zurückgeben.
  3. Einen Befehl lesen und dann eine Beschriftung ausgeben.
  4. Einen Scanner öffnen, um eine Anzahl anzufordern.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

countAbove(limit) zählt Messwerte, die echt größer als das Argument sind. Ein Protokoll enthält 11,5, 15,0, 15,0 und 18,5 °C. Was sollte eine direkte Prüfung für countAbove(15.0) erwarten?
  1. 0
  2. 1
  3. 2
  4. 3
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Mark.equals vergleicht code. Eine Liste enthält Markierungen mit den Codes „A“, „B“, „A“; die beiden A-Markierungen sind separate Objekte. Eine zweite Liste enthält nur eine Referenz auf die erste A-Markierung. Was bleibt nach marks.removeAll(second)?
  1. Nur die Markierung mit dem Code „B“.
  2. Die B-Markierung und die zweite A-Markierung.
  3. Die beiden Markierungen mit dem Code „A“.
  4. Keine Markierungen in der Liste.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 4: Durchdenke Kopien, Verträge und Entwurfsänderungen10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Eine Liste benötigt ganzzahlige Einträge, die fehlen dürfen. Welcher Elementtyp erlaubt es, entweder eine Zahl oder null zu speichern?
  1. int
  2. Integer
  3. double
  4. boolean
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Node enthält veränderliche Daten. Es existieren nur die gezeigten Referenzen. Wie viele Knotenobjekte sind in der letzten Zeile über first oder die Liste erreichbar?

Node first = new Node();
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(first);
first = new Node();
nodes.add(first);
nodes.remove(0);
  1. Ein Knotenobjekt.
  2. Zwei Knotenobjekte.
  3. Drei Knotenobjekte.
  4. Keine Knotenobjekte.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Locker.find(id) gibt derzeit ein neu konstruiertes veränderliches Item mit denselben Feldwerten wie der passende gespeicherte Gegenstand zurück. Aufrufender Code verwendet das Ergebnis, um die Beschriftung des gespeicherten Gegenstands zu ändern. Welches Verhalten muss die Methode bieten, damit sich diese Änderungen auf das Schließfach auswirken?
  1. Eine flache Kopie der Schließfachliste zurückgeben.
  2. Einen neuen Gegenstand mit der geänderten Beschriftung zurückgeben.
  3. Die Referenz auf den passenden gespeicherten Gegenstand zurückgeben.
  4. Nach jeder Beschriftungsänderung eine weitere Kopie zurückgeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Zwei Schedule-Objekte speichern ein gemeinsam genutztes veränderliches SimpleDate-Objekt in je einem Feld. Ein Entwickler macht nur die Felder der Terminpläne final. Was geschieht, wenn das ursprüngliche Datumsobjekt über seinen Setter geändert wird?
  1. Nur der Terminplan, dessen Getter aufgerufen wurde, sieht die Änderung.
  2. Keiner der Terminpläne sieht das geänderte Datum.
  3. Jeder Terminplan behält eine eigene Kopie des alten Datums.
  4. Beide Terminpläne sehen weiterhin das geänderte Datum.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Bibliothek verwaltet eine private Liste veränderlicher Document-Objekte. matching(prefix) erstellt eine neue Liste, die vorhandene passende Dokumente enthält. Aufrufender Code entfernt ein Ergebnis und ändert den Titel eines anderen Ergebnisses. Welche Wirkung kann das auf die Bibliothek haben?
  1. Die Zusammensetzung ändert sich; jeder gespeicherte Titel bleibt gleich.
  2. Die Zusammensetzung der Bibliothek und der gespeicherte Titel ändern sich.
  3. Weder die Zusammensetzung noch ein gespeicherter Titel ändert sich.
  4. Die Zusammensetzung bleibt gleich; ein gespeicherter Titel ändert sich.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Was stellt x == y für die Objektreferenzen x und y ungleich null fest, wenn der Ausdruck true ist?
  1. Ihre toString()-Ergebnisse definieren Gleichheit.
  2. Sie verweisen auf dasselbe Objekt.
  3. Ihre Klassen überschreiben equals gemeinsam.
  4. Ihre Felder wurden nie geändert.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Methode Score.equals(Object) akzeptiert einen anderen Punktwert, wenn die absolute Differenz ihrer ganzzahligen Werte höchstens 2 beträgt. Die Punktwerte x, y und z enthalten 10, 12 und 14. Kein Wert ändert sich. Welches Problem mit dem Gleichheitsvertrag zeigt sich über diese drei Objekte hinweg?
  1. Die Transitivität ist verletzt: Die ersten beiden Paare stimmen überein, die Endpunkte aber nicht.
  2. Die Symmetrie ist verletzt: Das Vertauschen desselben Paars ändert sein Vergleichsergebnis.
  3. Die Reflexivität ist verletzt: Ein Punktwert kann nicht gleich seiner eigenen Referenz sein.
  4. Die Konsistenz ist verletzt: Die Wiederholung eines Paars ergibt jedes Mal ein anderes Ergebnis.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

record Stamp(String code, int batch) {} verwendet generierte Gleichheits- und Hashcode-Methoden. Eine Menge erhält new Stamp("R", 4), ein separates new Stamp("R", 4) und dann new Stamp("R", 5). Wie viele Einträge enthält sie?
  1. 1
  2. 3
  3. 2
  4. 0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Aufgabenanwendung benötigt einen neuen Befehl, der die längste gespeicherte Dauer zurückgibt. Welche Implementierungsreihenfolge ermöglicht eine direkte Prüfung, bevor die Konsoleninteraktion ergänzt wird?
  1. Zeitdauern in der Oberfläche lesen und ausgeben, dann den Code in die Logik kopieren.
  2. Eine Logikabfrage ergänzen, bekannte Zeitdauern prüfen und dann den Oberflächenbefehl verbinden.
  3. Konsoleneingaben zur Logikabfrage ergänzen und sie dann von jedem Befehl aufrufen.
  4. Die private Liste öffentlich machen und die Oberfläche direkt darin suchen lassen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine Werteklasse verwendet Double.compare(level, other.level) == 0 in equals. Eine vorgeschlagene Hashcode-Methode gibt immer 1 zurück. Eine andere gibt unterschiedliche Codes für verschiedene Objekte zurück, deren Werte für level beide NaN sind. Wie verhalten sich diese Vorschläge zum Gleichheitsvertrag?
  1. Die Konstante ist unzulässig; unterschiedliche Codes für NaN-Objekte sind erforderlich.
  2. Beide sind zulässig, weil verschiedene Objekte beliebige Hashcodes haben dürfen.
  3. Die Konstante ist zulässig; unterschiedliche Codes für gleiche NaN-Werte sind unzulässig.
  4. Beide sind unzulässig, weil jeder double-Wert einen eindeutigen Code braucht.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

61 Schlüsselbegriffe, jeweils mit Definition

A

Aggregat
Ein Aggregat ist eine zusammenfassende statistische Größe wie Summe, Anzahl oder Maximum, die Werte zusammenfasst und dabei einen Teil der Informationen über sie verliert.
Dieselben Aggregate können Listen mit unterschiedlichen Anzahlen oberhalb eines Schwellenwerts beschreiben, sodass diese Abfrage weitere Daten benötigt.
Alias
Ein Alias ist eine weitere Variable oder Referenz, die dasselbe Objekt erreicht und damit mehrere Wege zu einem gemeinsam genutzten Zustand ermöglicht.
Nach Box second = first; ist second ein Alias und sein Setter ändert die Box, die über first erreicht wird.
Argument
Ein Argument ist ein an einer Aufrufstelle übergebener Wert, der einen passenden Parameter der aufgerufenen Methode oder des Konstruktors initialisiert.
In gate.allowIn(ana) ist das Argument der aus ana gelesene Referenzwert, den der Parameter kopiert.
ArrayList
Eine ArrayList ist ein Listenobjekt mit veränderlicher Größe und geordneten Einträgen; Objekteinträge sind Referenzen, sodass das Hinzufügen ihre Felder nicht kopiert.
Eine ArrayList<Parcel> kann drei Paketreferenzen enthalten und einer Schleife ermöglichen, ihre Gewichte in Kilogramm zu addieren.
Autoboxing
Autoboxing ist die automatische Umwandlung eines primitiven Werts in die entsprechende Wrapperreferenz durch Java bei einer Zuweisung oder einem Aufruf.
Durch Autoboxing übergibt numbers.add(12) einen Integer-Wert, wenn numbers eine ArrayList<Integer> ist.
Automatische Speicherbereinigung
Die automatische Speicherbereinigung ist die automatische Freigabe des Speichers unerreichbarer Objekte durch Java zu einem von der Laufzeitumgebung gewählten Zeitpunkt.
Ein Zähler kann von der automatischen Speicherbereinigung entfernt werden, nachdem das laufende Programm seinen letzten Weg zu ihm verloren hat.

B

Benutzeroberfläche
Eine Benutzeroberfläche ist der Programmcode, der mit dem Benutzer interagiert, etwa eine Befehlsschleife, die Text liest und Ergebnisse ausgibt.
Eine Benutzeroberfläche liest eine Temperatur, ruft die Methode add eines Temperaturprotokolls auf und zeigt gegebenenfalls eine Ablehnungsmeldung an.
Besitzerobjekt
Ein Besitzerobjekt verwaltet Zustand über eigene Methoden, etwa eine Playlist, die das Hinzufügen zu ihrer privaten Songliste kontrolliert.
Die Playlist ist das Besitzerobjekt, das für die Durchsetzung einer Höchstanzahl in seiner Methode add verantwortlich ist.

C

Collection-Abfrage
Eine Collection-Abfrage wählt Informationen aus gespeicherten Elementen aus oder berechnet sie und gibt häufig einen Wert, ein Objekt oder eine neue Ergebnisliste zurück.
Die Collection-Abfrage songsLongerThan(200) erstellt eine neue Liste und behält dabei die Referenzen auf passende Songs.
ConcurrentModificationException
Eine ConcurrentModificationException kann anzeigen, dass eine Collection während einer iteratorbasierten Traversierung strukturell geändert wurde.
Das direkte Entfernen aus einer Liste während einer for-each-Schleife kann vor Abschluss der Traversierung eine ConcurrentModificationException verursachen.

D

Datenvalidierung
Die Datenvalidierung prüft, ob ein übergebener Wert die Regeln der Anwendung erfüllt, bevor er in den gespeicherten Zustand übernommen wird.
Die Datenvalidierung in einem Dauerprotokoll kann einen negativen Wert unabhängig davon ablehnen, welche Oberfläche die Methode aufruft.
Defensive Kopie
Eine defensive Kopie ist ein separates Objekt, das verhindert, dass externer Code geschützten Zustand über eine gemeinsam genutzte veränderliche Referenz ändert.
Ein Geburtstags-Getter gibt eine defensive Kopie zurück, sodass ein Datums-Setter im aufrufenden Code den Geburtstag der Person unverändert lässt.
Delegation
Delegation bedeutet, ein anderes Objekt über dessen Methoden mit einer Aufgabe zu betrauen, sodass eine Regel bei dem Objekt bleibt, dem die Daten gehören.
Eine Person nutzt Delegation, wenn olderThan für den Datumsvergleich birthday.before(other.birthday) aufruft.
Direkte Prüfung
Eine direkte Prüfung ruft Logikmethoden mit bekannten Eingaben auf und vergleicht ihre Ergebnisse mit erwarteten Antworten, ohne eine Benutzeroberfläche zu durchlaufen.
Eine direkte Prüfung kann vier Messwerte hinzufügen und den Mittelwert überprüfen, ohne Befehlstext einzulesen.

E

Empfängerobjekt
Das Empfängerobjekt ist das Objekt, auf dem eine Instanzmethode aufgerufen wird; innerhalb dieser Methode verweist this auf dieses Objekt.
In earlier.before(later) ist earlier das Empfängerobjekt; das andere Datum wird als Argument übergeben.
Erreichbarkeit
Erreichbarkeit bedeutet, dass das laufende Programm einer nutzbaren Referenzkette zu einem Objekt folgen kann, gegebenenfalls über Felder oder Listen.
Eine Liste erhält die Erreichbarkeit eines Pakets, selbst nachdem die lokale Variable, die es ursprünglich enthielt, null geworden ist.

F

Fabrikmethode
Eine Fabrikmethode liefert dem aufrufenden Code ein Objekt, indem sie entweder eine neue Instanz erzeugt oder eine bereits vorhandene wiederverwendet.
Die Fabrikmethode issue() eines Ticketautomaten konstruiert ein Ticket mit seiner nächsten Nummer und gibt es zurück.
final-Referenzfeld
Einem final-Referenzfeld kann nach der Initialisierung keine andere Referenz zugewiesen werden, auch wenn das referenzierte Objekt weiterhin veränderlich sein kann.
Ein final-Referenzfeld mit einer Liste verhindert, dass die Liste ersetzt wird, unterbindet aber keine Aufrufe von add.
Flache Kopie
Eine flache Kopie erzeugt eine separate äußere Collection, kopiert aber deren Elementreferenzen, sodass die enthaltenen Objekte gemeinsam genutzt bleiben.
Eine flache Kopie einer Songliste kann unabhängig einen Eintrag verlieren und trotzdem jeden veränderlichen Song gemeinsam nutzen.
for-each-Schleife
Eine for-each-Schleife besucht die Elemente einer Collection der Reihe nach und weist jeden Elementwert zur Verarbeitung einer Schleifenvariablen zu.
Eine for-each-Schleife über Pakete kann jedes Ergebnis von getWeight() addieren, ohne die Listenzugehörigkeit zu ändern.

G

Gleichheitsrelevante Felder
Gleichheitsrelevante Felder sind die Daten, die festlegen, wann zwei Instanzen für den Zweck eines Programms denselben Wert darstellen.
Titel und Jahr können gleichheitsrelevante Felder für Bücher sein, sodass eine Änderung nur der ausleihenden Person diese Gleichheit unverändert lässt.

H

Hashcode
Ein Hashcode ist eine ganze Zahl, die mögliche Treffer in einer hashbasierten Collection eingrenzt; gleiche Objekte müssen gleiche Codes erzeugen.
Ein passender Buch-Hashcode lässt sich mit Objects.hash(title, year) berechnen, wenn diese Felder die Gleichheit definieren.
Hashkollision
Eine Hashkollision entsteht, wenn ungleiche Objekte denselben Hashcode erzeugen. Das ist zulässig, weil die Gleichheitsprüfung eine Übereinstimmung weiterhin bestätigt.
Eine Hashkollision führt unterschiedliche Werte in einer Menge nicht zusammen, wenn equals sie korrekt unterscheidet.
HashMap-Schlüssel
Ein HashMap-Schlüssel identifiziert einen zugeordneten Wert mithilfe von Hashcode und Gleichheitsprüfung; sein gleichheitsrelevanter Zustand sollte während der Speicherung stabil bleiben.
Ein HashMap-Schlüssel aus Titel und Jahr kann mit einem separaten gleichen Buchobjekt eine Regalbeschriftung abrufen.
HashSet
Ein HashSet speichert unterschiedliche Elemente mithilfe ihrer Hashcodes und der Gleichheitsprüfung, sodass das Hinzufügen eines gleichen Werts keinen weiteren Eintrag erzeugt.
Ein HashSet<Book> behält einen Eintrag für zwei separate gleiche Bücher, wenn ihre Gleichheitsprüfung und Hashcodes zusammenpassen.

I

Identität
Identität bedeutet, dass es sich um dasselbe Objekt handelt; sie wird bei zwei Referenzen mit == geprüft. Übereinstimmende Feldwerte allein belegen sie nicht.
Zwei separat konstruierte Bücher können gleiche Titel und Jahre haben und dennoch unterschiedliche Identitäten besitzen.
Inhaltliche Gleichheit
Inhaltliche Gleichheit bedeutet, dass die durch die Gleichheitsregel einer Klasse ausgewählten Felder übereinstimmen, auch wenn die Objekte unterschiedliche Identitäten haben.
Ein Buchkatalog kann inhaltliche Gleichheit anhand von Titel und Jahr definieren und dabei die Person ignorieren, die das Buch gerade ausgeliehen hat.

K

Kapselung
Kapselung hält Zustand hinter den Methoden einer Klasse verborgen, sodass die Klasse Änderungen kontrollieren und Regeln für ihre gespeicherten Daten durchsetzen kann.
Die Kapselung einer Playlist wird geschwächt, wenn ein Getter die private Liste zurückgibt und aufrufender Code sie direkt leeren kann.
Komposition
Komposition modelliert eine Hat-ein-Beziehung, indem ein Objekt Felder erhält, die auf andere Objekte verweisen, etwa auf den Geburtstag einer Person.
Durch Komposition kann ein Kurs seine Lehrkraft als Personenobjekt speichern und den Namen über eine Methode abrufen.
Konsistenz
Konsistenz verlangt, dass wiederholte Gleichheitsprüfungen übereinstimmen, solange die verglichenen Informationen unverändert bleiben, sodass Ergebnisse verlässlich sind.
Konsistenz erlaubt eine Ergebnisänderung nach der Änderung eines gleichheitsrelevanten Felds, aber nicht, solange die verglichenen Daten fest bleiben.
Konstruktorinjektion
Die Konstruktorinjektion übergibt einem Objekt seine benötigten Partnerobjekte als Konstruktorargumente, sodass es vorhandene Objekte gemeinsam nutzen kann.
Durch Konstruktorinjektion erhält eine Benutzeroberfläche einen Scanner und ein Protokoll, statt für jeden Befehl ein separates Protokoll zu erzeugen.
Kopiermethode
Eine Kopiermethode gibt ein neues Objekt zurück, das aus den Daten eines vorhandenen Objekts initialisiert wurde und die durch ihren Entwurf versprochene Unabhängigkeit bietet.
Die Kopiermethode eines Zählers verwendet new Counter(value), sodass das Erhöhen der Kopie das Original unverändert lässt.
Kurzschlussauswertung
Die Kurzschlussauswertung überspringt die rechte Seite von &&, wenn die linke falsch ist. So kann eine vorherige Prüfung einen späteren Aufruf absichern.
Durch Kurzschlussauswertung überspringt name != null && name.length() > 0 den Methodenaufruf bei null.

L

Listenzugehörigkeit
Die Listenzugehörigkeit beschreibt, welche Einträge eine Liste enthält; ihre Änderung ist von der Änderung der Felder eines gespeicherten Objekts zu unterscheiden.
Das Entfernen eines Songs ändert die Listenzugehörigkeit in einer Liste, während eine andere Playlist weiterhin auf denselben Song verweisen kann.

M

Methodenüberladung
Eine Methodenüberladung ist eine weitere Methode mit demselben Namen, aber anderen Parametertypen; sie ersetzt die ursprüngliche Deklaration nicht.
Das Schreiben von equals(Book other) erzeugt eine Methodenüberladung, während equals(Object) für Collections verfügbar bleibt.
Methodenüberschreibung
Eine Methodenüberschreibung ersetzt das Verhalten einer geerbten Instanzmethode durch eine passende Deklaration, etwa equals(Object) in einer neuen Klasse.
Die Annotation @Override ermöglicht dem Compiler zu prüfen, ob die Gleichheitsmethode eines Buchs eine gültige Methodenüberschreibung ist.

N

Neuzuweisung
Eine Neuzuweisung speichert einen neuen Wert in einer vorhandenen Variablen; bei einer Referenzvariablen ändert sie, welches Objekt die Variable erreicht.
Die Neuzuweisung current = new Counter(); lenkt current um, während andere Aliasse den alten Zähler behalten.
NullPointerException
Eine NullPointerException ist ein Laufzeitfehler durch Operationen, die ein fehlendes Objekt benötigen, etwa ein Instanzaufruf oder Unboxing über null.
Der Aufruf name.length() löst eine NullPointerException aus, wenn die Variable name den Wert null enthält.
Nullreferenz
Eine Nullreferenz ist der Wert null, der zu keinem Objekt führt und in einer Referenzvariablen, aber nicht in einer Variablen eines primitiven Typs enthalten sein kann.
Eine Nullreferenz in String name kann keinen Aufruf von length() empfangen, während ein leerer String die Länge 0 zurückgeben kann.

Ö

Öffentlicher Vertrag
Ein öffentlicher Vertrag ist das zugesagte Verhalten, auf das sich aufrufender Code bei den öffentlichen Methoden einer Klasse unabhängig von deren privater Speicherung verlässt.
Eine Änderung der Speicherung hält den öffentlichen Vertrag nur ein, wenn jede unterstützte Temperaturabfrage weiterhin funktioniert.

P

Parameter
Ein Parameter ist eine von einer Methode oder einem Konstruktor deklarierte Variable, die bei einem Aufruf den kopierten Wert eines Arguments erhält.
In allowIn(Person visitor) erhält der Parameter visitor die vom aufrufenden Code übergebene Personenreferenz.
Primitiver Datentyp
Ein primitiver Datentyp speichert gewöhnliche Daten wie eine Zahl oder einen Wahrheitswert direkt; Java hat acht primitive Datentypen.
Der primitive Datentyp int ermöglicht, dass count den Wert 12 direkt enthält. Eine Zuweisung an eine andere Variable kopiert daher 12.
Programmlogik
Die Programmlogik speichert Daten und wendet die Regeln der Anwendung an; sie bietet Methoden, die unabhängig von der Konsoleninteraktion arbeiten können.
Die Programmlogik berechnet den Temperaturmittelwert als Zahl, die eine Konsolen- oder Weboberfläche anzeigen kann.

R

Record
Ein Record ist eine Java-Datenklasse mit finalen Komponentenfeldern und generierten Zugriffsmethoden, Gleichheitsprüfung, Hashcode-Berechnung und String-Darstellung.
Der Record Book(String title, int year) vergleicht beide Komponenten und bietet die Zugriffsmethoden title() und year().
Referenz
Eine Referenz ist ein Wert, der zu einem Objekt führt; eine Kopie bietet einer anderen Variablen einen Weg zu demselben Objekt.
Die mit Counter second = first; zugewiesene Referenz ermöglicht beiden Variablen, den ursprünglichen Zähler zu erreichen.
Referenzfeld
Ein Referenzfeld ist ein Feld eines Referenztyps, das sein Besitzerobjekt mit einem anderen Objekt verbindet oder null enthält, wenn kein Objekt vorhanden ist.
Das Referenzfeld birthday in Person kann zu einem SimpleDate führen, das Datumsvergleiche durchführt.
Referenztyp
Ein Referenztyp ist ein Klassen-, Array- oder Interface-Typ, dessen Variablen Referenzen auf Objekte oder den Wert null enthalten können.
String ist ein Referenztyp, sodass eine Variable auf ein Textobjekt verweisen oder keine Objektreferenz enthalten kann.
Reflexivität
Reflexivität verlangt, dass jedes Objekt ungleich null beim Vergleich mit sich selbst gleich ist, sodass eine gültige Gleichheitsmethode für x.equals(x) true zurückgibt.
Eine anfängliche Prüfung this == other in equals erfüllt die Reflexivität, bevor Feldwerte verglichen werden.
Rückgabewert
Ein Rückgabewert ist der Wert, den eine Methode an den aufrufenden Code zurückgibt; bei einem Objektergebnis ist es eine Referenz oder null.
Der Rückgabewert von findByName eines Registers kann zu einer gespeicherten Person führen, die der aufrufende Code und das Register dann gemeinsam nutzen.

S

Standardwert eines Felds
Ein Standardwert eines Felds ist der Anfangswert, den Java einem Feld zuweist, etwa null für Referenzen oder false für boolesche Werte.
Der Standardwert eines nicht initialisierten Felds vom Typ ArrayList<Song> ist null, sodass noch keine Liste existiert.
String-Internierung
Die String-Internierung nutzt String-Objekte für übereinstimmende Literale gemeinsam, sodass zwei identische Literalausdrücke dieselbe Referenz haben können.
String-Internierung kann "oak" == "oak" wahr machen und einen Fehler verbergen, der bei gleichem Text aus einer Eingabe auftritt.
Suchergebnis
Ein Suchergebnis ist der von einer Abfrage zurückgegebene passende Wert; eine Objektsuche kann die gespeicherte Referenz oder bei fehlendem Treffer null zurückgeben.
Das Suchergebnis für einen Namen kann eine in einem Register gespeicherte Person sein, die sich über die zurückgegebene Referenz aktualisieren lässt.
Symmetrie
Symmetrie verlangt, dass das Vertauschen zweier Objekte ungleich null bei einem Gleichheitsvergleich das Ergebnis true oder false erhält.
Die Symmetrie ist verletzt, wenn ein Buch seinen Titelstring als gleich akzeptiert, der String das Buchobjekt aber ablehnt.

T

Transitivität
Transitivität verlangt, dass ein erstes Objekt auch einem dritten gleich ist, wenn es einem zweiten gleich ist und das zweite dem dritten gleicht.
Durch Transitivität gehören drei Bücher mit denselben gleichheitsrelevanten Feldern zur selben Gruppe gleicher Werte.

U

Unboxing
Unboxing wandelt den gespeicherten Wert eines Wrapperobjekts in seinen primitiven Datentyp um; bei einem Wrapper mit dem Wert null schlägt es mit einer NullPointerException fehl.
Unboxing findet in int count = boxed; statt, sodass boxed auf ein tatsächliches Integer-Objekt verweisen muss.
Unveränderlichkeit
Unveränderlichkeit bedeutet, dass sich der Zustand eines Objekts nach der Konstruktion nicht ändern kann, sodass seine gemeinsame Nutzung keine gemeinsame Zustandsänderung erlaubt.
Die Unveränderlichkeit von Strings bedeutet, dass toUpperCase() die Zeichen des ursprünglichen Strings nicht ändern kann.

V

Veränderliches Objekt
Ein veränderliches Objekt lässt nach seiner Konstruktion Änderungen seines Zustands zu, sodass Aliasse Änderungen über eine andere Referenz beobachten können.
Ein Datum mit Settern ist ein veränderliches Objekt; wird es von zwei Personen gemeinsam genutzt, können sich beide gespeicherten Geburtstage ändern.

W

Wertübergabe
Bei der Wertübergabe erhält ein Methodenparameter eine eigene Kopie des Argumentwerts, bei einem Objektargument also eine kopierte Referenz.
Durch Wertübergabe kann eine Methode ihren Personenparameter umlenken, ohne die Variable des aufrufenden Codes umzulenken.
Wrapper-Cache
Ein Wrapper-Cache verwendet Wrapperobjekte für ausgewählte Werte wieder, sodass die Referenzidentität von der Wiederverwendung statt von numerischer Gleichheit abhängt.
Der Integer-Wrapper-Cache umfasst kleine Werte; verwende daher equals, statt dich auf == für Wrapper zu verlassen.
Wrapperklasse
Eine Wrapperklasse stellt einen primitiven Wert als Objekt dar, sodass Werte wie ganze Zahlen dort verwendet werden können, wo Referenztypen nötig sind.
Die Wrapperklasse Integer liefert den Elementtyp für ArrayList<Integer>; der primitive Typ int kann das nicht.

Z

Zustandsänderung
Eine Zustandsänderung verändert den Zustand eines vorhandenen Objekts, sodass die geänderten Feldwerte über alle Referenzen auf dieses Objekt sichtbar werden.
Der Aufruf parcel.setWeight(3.5) ist eine Zustandsänderung, die auch über eine Liste sichtbar ist, die dieses Paket enthält.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten