Objektreferenzen in Java: Werte, Aliasse und Gleichheit
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
- 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 ReferenzvariablenNenne 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 ObjekteVerfolge 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 umErkläre, was null bedeutet, erkenne, wo es vorkommt, verhindere eine NullPointerException und entscheide, wann null ein sinnvoller Wert ist.
- Vergleiche Variablen primitiver Typen mit Referenzvariablen
- 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 MethodenparameterSage 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ückSchreibe 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.
- Verwende Objekte als Methodenparameter
- 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 KlasseModelliere 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 ListenFü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 ObjektlisteSchreibe eine Klasse, die eine Objektliste als Feld verwaltet, sie initialisiert, Methoden zur Arbeit mit ihrem Inhalt anbietet und die Liste selbst verbirgt.
- Verwende andere Objekte zur Komposition einer Klasse
- 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 GleichheitErklä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 equalsErklä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.
- Unterscheide Identität und inhaltliche Gleichheit
- 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 ProgrammlogikEntscheide, 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 ProgrammlogikErgä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.
- Gliedere ein Programm in eine Benutzeroberfläche und Klassen für die Programmlogik
Objektreferenzen in Java einfach erklärt
1Verfolge 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.
| Deklaration | Inhalt 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
newerzeugt 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);
- Zeichne die Variable
aso, dass sie auf Objekt A mit dem Gewicht 2 zeigt. - Zeichne
bso, dass es auf dasselbe Objekt A zeigt. Es gibt weiterhin eine Box. - Ändere das Gewicht in Objekt A auf 5. Beide Wege führen nun zum Gewicht 5.
- Erzeuge Objekt B mit dem Gewicht 7 und richte nur
bdarauf 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 Diagramm | Wirkung über andere Aliasse |
|---|---|---|
b.setWeight(5) | Ein Feld im Objekt ändern | Sie sehen das neue Gewicht |
b = new Box(7) | Den Pfeil von b umlenken | Ihre bisherigen Pfeile bleiben bestehen |
b = null | Den Pfeil von b entfernen | Sie 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.
| Zweck | Sicherer Ausdruck |
|---|---|
| Auf Fehlen prüfen | name == null |
| Auf Vorhandensein prüfen | name != 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
nullbedeutet, 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.
2Verwende 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 Methode | Beobachtung im aufrufenden Code |
|---|---|
| Einem primitiven Parameter einen Wert zuweisen | Keine Änderung der aufrufenden Variablen |
| Das Objekt des Parameters ändern | Geänderter Zustand des gemeinsam genutzten Objekts |
| Einem Referenzparameter einen neuen Wert zuweisen | Keine Ä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.
thisbezeichnet 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.
3Verbinde 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 Konstruktor | Folge für die Referenz |
|---|---|
| Die übergebene Datumsreferenz speichern | Aufrufender Code und Person können das Datum gemeinsam nutzen |
| Ein neues Datum aus den übergebenen Werten erzeugen | Die Person beginnt mit einem eigenen Datum |
| Ein unveränderliches Datum speichern | Gemeinsame 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
equalsab. - 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 Kopie | Wirkung auf die Playlist |
|---|---|
| Einen Eintrag entfernen | Die Zusammensetzung der Playlist bleibt gleich |
| Die Kopie leeren | Die Zusammensetzung der Playlist bleibt gleich |
| Einen gemeinsam genutzten Song ändern | Die 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.
4Definiere, 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 Werte | Bedeutung von == | Bedeutung von equals |
|---|---|---|
| Primitive Werte | Primitive Werte vergleichen | Keine Instanzmethode auf einem primitiven Wert |
| Referenzen | Dasselbe Objekt oder beide null | Die Gleichheitsregel der Klasse |
| Strings | Dasselbe String-Objekt | Übereinstimmender Zeicheninhalt |
Klasse, die Object.equals erbt | Dasselbe Objekt | Dasselbe 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ährendequalsder Regel der Klasse folgt. Object.equalsvergleicht die Identität, bis die Methode überschrieben wird.- Verwende
String.equalsfü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)undy.equals(x)stimmen überein. - Transitivität: Wenn
xgleichyundygleichzist, istxgleichz. - 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 ersetztequals(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-Regel | Folge für den Vertrag |
|---|---|
| Dieselben gleichheitsrelevanten Felder verwenden | Gleiche Objekte erhalten gleiche Codes |
| Ein Feld hinzufügen, das die Gleichheitsprüfung ignoriert | Gleiche Objekte können unterschiedliche Codes erhalten |
| Weniger gleichheitsrelevante Felder verwenden | Zulässig, kann aber mehr Kollisionen erzeugen |
| Immer 1 zurückgeben | Zulä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 duequalsü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.
5Gib 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.
| Aufgabe | Zuständige Klasse |
|---|---|
| Befehlstext lesen | Benutzeroberfläche |
| Einen Messwert speichern | Temperaturprotokoll |
| Mittelwert oder Maximum berechnen | Temperaturprotokoll |
| Ein Ergebnis runden und ausgeben | Benutzeroberfläche |
| Eine unmögliche Temperatur ablehnen | Temperaturprotokoll |
| Ungültige Eingaben dem Benutzer erklären | Benutzeroberflä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.
mainerzeugt 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
1Verfolge Variablen, ohne die Objekte aus dem Blick zu verlieren
15 Schritte
- 1Einfü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.
- 2ErklärungEin Wert kann ein Datum oder eine Referenz sein
Java hat acht primitive Datentypen:
byte,short,int,long,float,double,charundboolean. Ihre Variablen enthalten Daten direkt. Klassen und Arrays sind Referenztypen; ihre Variablen enthalten Referenzen odernull.In
int count = 13;ist der Wert 13. InTally t = new Tally(13);ist der Wert inteine Referenz auf ein Objekt, das seine eigenen Daten enthält. - 3BeispielEinen 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.startbleibt 13, weil keine Anweisung der Variablen einen neuen Wert zuweist. Zeichne zwei Kästchen mit separaten Zahlen; es gibt kein gemeinsam genutztes Objekt. - 4BeispielEinen Referenzwert kopieren
Angenommen,
Tallyspeichert eine ganze Zahl,addOne()erhöht sie undvalue()liest sie.Tally first = new Tally(13); Tally next = first; next.addOne();Ein
newerzeugt einen Zähler. Die Zuweisung gibtnexteinen weiteren Weg dorthin. Der Aufruf vonaddOne()ändert den gespeicherten Wert auf 14, den nun auchfirst.value()liest. Die zweite Variable ist kein zweiter Zähler. - 5HerausforderungEin Objekt von seinem Alias trennen
Was gibt
a.value()bei denselbenTally-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
newerzeugt 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. - 6VerbindungManche gemeinsam genutzten Objekte sind unveränderlich
Strings und Wrapper wie
Integersind Referenztypen. Autoboxing wandelt primitive Werte für Anwendungen wieArrayList<Integer>in Wrapper um.Strings sind unveränderlich: Wenn
leftundright„cedar“ gemeinsam nutzen, ändert die Zuweisungright = right.toUpperCase()die ursprünglichen Zeichen nicht.leftbleibt „cedar“. Das zurückgegebene Ergebnis in Großbuchstaben wird nurrightzugewiesen. - 7AnalogieÄ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.
- 8BeispielZuerst gemeinsam genutzten Zustand ändern
Die Klasse
Cratespeichere eine Masse in Kilogramm und habe die MethodensetMassundmass.Crate a = new Crate(3); Crate b = a; b.setMass(6);Zeichne
aundbso, dass beide auf eine Kiste zeigen. Der Setter ändert die Masse in dieser Kiste von 3 auf 6. Sowohla.mass()als auchb.mass()geben nun 6 zurück; der Setter hat keine der beiden Variablen umgelenkt. - 9BeispielDann eine Variable umlenken
Setze die Ablaufverfolgung der Kiste fort:
b = new Crate(9);Erzeuge ein zweites Objekt mit der Masse 9 und lenke nur
bum. Die Variableazeigt 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. - 10HerausforderungDen 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
cweiterhin erreichbar und kann daher nicht von der Speicherbereinigung entfernt werden. Die frühere Zuweisung hat die Referenz nachckopiert; das Setzen vonaauf null hat nur einen Weg entfernt. Die separate Kiste mit 9 kg bleibt überberreichbar. - 11KernaussageEin 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.
- 12ErklärungNull bedeutet, dass das Ziel fehlt
nullist kein leeres Objekt. EinStringmit dem Wert null hat kein Objekt, auf demlength()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 eineNullPointerExceptionaus, wenn das Unboxing versucht, einen primitiven Wert aus dem fehlenden Wrapper zu erhalten. - 13TippDen 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.
- 14HerausforderungDen 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&&überspringtmass(), 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. - 15ZusammenfassungEinen 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.
2Verfolge ein Objekt beim Übergeben an eine Methode und bei der Rückgabe
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungEinen Aufruf und sein Ergebnis verfolgen
- 2ErklärungEin Parameter erhält seinen eigenen Wert
- 3BeispielDas gemeinsam genutzte Objekt ändern
- 4BeispielDen lokalen Weg neu zuweisen
- 5HerausforderungKann eine Methode Variablen des aufrufenden Codes vertauschen?
- 6VerbindungDas Empfängerobjekt mit seinem Argument vergleichen
- 7ErklärungDer Rückgabetyp verspricht keine Kopie
- 8BeispielEin unabhängiges Einstellrad zur Rückgabe erzeugen
- 9HerausforderungEinen Eintrag finden, der aktualisiert werden kann
- 10TippEin Getter kann einen verborgenen Alias offenlegen
- 11HerausforderungEine Rückgabe mit einem Parameteraufruf verbinden
- 12ZusammenfassungEine Methode an beiden Übergabestellen untersuchen
3Erstelle und schütze eine Collection von Objekten
16 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungDie Zustandsebenen trennen
- 2ErklärungEine Hat-ein-Beziehung modellieren
- 3BeispielDie Datumsregel delegieren
- 4HerausforderungDas übergebene Datum schützen
- 5ErklärungEin Eintrag ist ein weiterer Weg
- 6BeispielEine Summe mit einer Schleife berechnen
- 7BeispielDas ausgewählte Objekt behalten
- 8HerausforderungEin fehlendes Ergebnis zum richtigen Zeitpunkt zurückgeben
- 9TippEntfernen und Traversierung trennen
- 10ErklärungDem Besitzerobjekt eine initialisierte Liste geben
- 11BeispielNützliche Operationen anbieten
- 12HerausforderungEine Listenkopie von ihren Elementen unterscheiden
- 13VerbindungBeide Richtungen schützen
- 14BeispielIn ein neues Ergebnis filtern
- 15HerausforderungEine Kopie durch ein anderes Objekt verfolgen
- 16ZusammenfassungDie Übergabestellen der Collection prüfen
4Entwickle eine Gleichheitsprüfung, auf die Collections vertrauen können
16 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungFestlegen, was eine Übereinstimmung bedeutet
- 2BeispielGleicher Text in separaten Objekten
- 3TippDie Regel nicht durch Zwischenspeicherung bestimmen lassen
- 4HerausforderungEine Klasse anhand des Inhalts vergleichen
- 5ErklärungFelder und Signatur wählen
- 6BeispielIdentität akzeptieren und falsche Typen ablehnen
- 7BeispielDie ausgewählten Felder vergleichen
- 8HerausforderungEine Vertragsverletzung erkennen
- 9KernaussageMehr als ein passendes Paar prüfen
- 10ErklärungHashcodes grenzen die möglichen Treffer ein
- 11BeispielDen passenden Hashcode erzeugen
- 12HerausforderungKollision und Gleichheit auseinanderhalten
- 13BeispielDie Wirkung eines geänderten Schlüssels erkennen
- 14VerbindungEin einfacher Record liefert beide Methoden
- 15HerausforderungDie Menge mit generierter Gleichheitsprüfung verfolgen
- 16ZusammenfassungEine Werteklasse überprüfen
5Ergänze eine Funktion, ohne Eingaben und Regeln zu vermischen
11 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungRegeln mit einem Dialog verbinden
- 2ErklärungDie beiden Aufgaben benennen
- 3BeispielGemeinsam genutzte Objekte verbinden
- 4BeispielDas Ergebnis zur Anzeige zurückgeben
- 5HerausforderungEine Ablehnung in der richtigen Klasse platzieren
- 6ErklärungDer neuen Abfrage eine Logikmethode geben
- 7BeispielMit bekannten Eingaben prüfen
- 8HerausforderungDen Vorschlag zur Speicherung beurteilen
- 9TippDas Programm in geprüften Schritten erweitern
- 10HerausforderungZu einer anderen Oberfläche wechseln
- 11ZusammenfassungEine neue Funktion an ihrer Übergabestelle prüfen
Objektreferenzen in Java: Übungsaufgaben
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;
Antwort zeigenAntwort ausblenden
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();
Antwort zeigenAntwort ausblenden
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?Antwort zeigenAntwort ausblenden
&& ü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
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?Antwort zeigenAntwort ausblenden
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();
Antwort zeigenAntwort ausblenden
Frage 6 von 10
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?Antwort zeigenAntwort ausblenden
Frage 7 von 10
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?Antwort zeigenAntwort ausblenden
Frage 8 von 10
Was geben diese beiden Ausdrücke zurück?
String word = new String("maple");
word == "maple"
word.equals("maple")
Antwort zeigenAntwort ausblenden
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
Object geerbte Gleichheitsmethode?Antwort zeigenAntwort ausblenden
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
Antwort zeigenAntwort ausblenden
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();
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;
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);
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
return null in einem else nach der Namensprüfung innerhalb der Schleife. Wohin gehört diese Rückgabe?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?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"))
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
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?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;
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
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?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);
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
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?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?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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
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?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)?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
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);
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
x == y für die Objektreferenzen x und y ungleich null fest, wenn der Ausdruck true ist?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
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?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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
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?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
Aggregat
Alias
Box second = first; ist second ein Alias und sein Setter ändert die Box, die über first erreicht wird.Argument
gate.allowIn(ana) ist das Argument der aus ana gelesene Referenzwert, den der Parameter kopiert.ArrayList
ArrayList<Parcel> kann drei Paketreferenzen enthalten und einer Schleife ermöglichen, ihre Gewichte in Kilogramm zu addieren.Autoboxing
numbers.add(12) einen Integer-Wert, wenn numbers eine ArrayList<Integer> ist.Automatische Speicherbereinigung
B
Benutzeroberfläche
add eines Temperaturprotokolls auf und zeigt gegebenenfalls eine Ablehnungsmeldung an.Besitzerobjekt
add verantwortlich ist.C
Collection-Abfrage
songsLongerThan(200) erstellt eine neue Liste und behält dabei die Referenzen auf passende Songs.ConcurrentModificationException
ConcurrentModificationException verursachen.D
Datenvalidierung
Defensive Kopie
Delegation
olderThan für den Datumsvergleich birthday.before(other.birthday) aufruft.Direkte Prüfung
E
Empfängerobjekt
this auf dieses Objekt.earlier.before(later) ist earlier das Empfängerobjekt; das andere Datum wird als Argument übergeben.Erreichbarkeit
F
Fabrikmethode
issue() eines Ticketautomaten konstruiert ein Ticket mit seiner nächsten Nummer und gibt es zurück.final-Referenzfeld
add.Flache Kopie
for-each-Schleife
getWeight() addieren, ohne die Listenzugehörigkeit zu ändern.G
Gleichheitsrelevante Felder
H
Hashcode
Objects.hash(title, year) berechnen, wenn diese Felder die Gleichheit definieren.Hashkollision
equals sie korrekt unterscheidet.HashMap-Schlüssel
HashSet
HashSet<Book> behält einen Eintrag für zwei separate gleiche Bücher, wenn ihre Gleichheitsprüfung und Hashcodes zusammenpassen.I
Identität
== geprüft. Übereinstimmende Feldwerte allein belegen sie nicht.Inhaltliche Gleichheit
K
Kapselung
Komposition
Konsistenz
Konstruktorinjektion
Kopiermethode
new Counter(value), sodass das Erhöhen der Kopie das Original unverändert lässt.Kurzschlussauswertung
&&, wenn die linke falsch ist. So kann eine vorherige Prüfung einen späteren Aufruf absichern.name != null && name.length() > 0 den Methodenaufruf bei null.L
Listenzugehörigkeit
M
Methodenüberladung
equals(Book other) erzeugt eine Methodenüberladung, während equals(Object) für Collections verfügbar bleibt.Methodenüberschreibung
equals(Object) in einer neuen Klasse.@Override ermöglicht dem Compiler zu prüfen, ob die Gleichheitsmethode eines Buchs eine gültige Methodenüberschreibung ist.N
Neuzuweisung
current = new Counter(); lenkt current um, während andere Aliasse den alten Zähler behalten.NullPointerException
name.length() löst eine NullPointerException aus, wenn die Variable name den Wert null enthält.Nullreferenz
null, der zu keinem Objekt führt und in einer Referenzvariablen, aber nicht in einer Variablen eines primitiven Typs enthalten sein kann.String name kann keinen Aufruf von length() empfangen, während ein leerer String die Länge 0 zurückgeben kann.Ö
Öffentlicher Vertrag
P
Parameter
allowIn(Person visitor) erhält der Parameter visitor die vom aufrufenden Code übergebene Personenreferenz.Primitiver Datentyp
int ermöglicht, dass count den Wert 12 direkt enthält. Eine Zuweisung an eine andere Variable kopiert daher 12.Programmlogik
R
Record
Book(String title, int year) vergleicht beide Komponenten und bietet die Zugriffsmethoden title() und year().Referenz
Counter second = first; zugewiesene Referenz ermöglicht beiden Variablen, den ursprünglichen Zähler zu erreichen.Referenzfeld
birthday in Person kann zu einem SimpleDate führen, das Datumsvergleiche durchführt.Referenztyp
null enthalten können.String ist ein Referenztyp, sodass eine Variable auf ein Textobjekt verweisen oder keine Objektreferenz enthalten kann.Reflexivität
x.equals(x) true zurückgibt.this == other in equals erfüllt die Reflexivität, bevor Feldwerte verglichen werden.Rückgabewert
findByName eines Registers kann zu einer gespeicherten Person führen, die der aufrufende Code und das Register dann gemeinsam nutzen.S
Standardwert eines Felds
ArrayList<Song> ist null, sodass noch keine Liste existiert.String-Internierung
"oak" == "oak" wahr machen und einen Fehler verbergen, der bei gleichem Text aus einer Eingabe auftritt.Suchergebnis
Symmetrie
T
Transitivität
U
Unboxing
NullPointerException fehl.int count = boxed; statt, sodass boxed auf ein tatsächliches Integer-Objekt verweisen muss.Unveränderlichkeit
toUpperCase() die Zeichen des ursprünglichen Strings nicht ändern kann.V
Veränderliches Objekt
W
Wertübergabe
Wrapper-Cache
equals, statt dich auf == für Wrapper zu verlassen.Wrapperklasse
Integer liefert den Elementtyp für ArrayList<Integer>; der primitive Typ int kann das nicht.Z
Zustandsänderung
parcel.setWeight(3.5) ist eine Zustandsänderung, die auch über eine Liste sichtbar ist, die dieses Paket enthält.- 1Java-Programme kompilieren und starten
- 2Bedingungen und Schleifen in Java
- 3Java-Methoden und Problemzerlegung
- 4Listen, Arrays und Strings in Java
- 5Java-Klassen und Objekte
- 6Objektreferenzen in Java
- 7HashMaps und Collections in Java
- 8Vererbung in Java
- 9Ausnahmen und Dateien in Java
- 10Modultests in Java
- 11Lambda-Ausdrücke und Streams in Java
Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.