Java Object-Klasse
Methoden, die jedes Java-Objekt von java.lang.Object erbt — toString, equals, hashCode, getClass, clone und finalize.
Jede Klasse in Java — ob Sie extends Object schreiben oder nicht — befindet sich in der Typhierarchie unterhalb von java.lang.Object. Das bedeutet, dass jede Referenz, die Sie jemals halten, ein kleines Bündel an Methoden besitzt: toString, equals, hashCode, getClass und einige mehr. Zu verstehen, was sie standardmäßig tun und wann Sie sie überschreiben sollten, ist der Unterschied zwischen Objekten, die gut mit Collections, Debuggern und Frameworks zusammenarbeiten — und solchen, die das nicht tun.
Dieses Kapitel ist die Übersichtskarte des Gebiets. Die nächsten Kapitel befassen sich einzeln mit den spezifischen Methoden (equals/hashCode, toString, clone).
Implizite Erweiterung
Schreiben Sie eine Klasse ohne extends-Klausel:
public class Box {
int contents;
}Der Compiler behandelt dies, als hätten Sie public class Box extends Object geschrieben. Deshalb können Sie eine Box an alles übergeben, das als Object deklariert ist — jeder Referenztyp ist letztendlich ein Object.
Die geerbten Methoden auf einen Blick
| Methode | Was der Standard tut |
|---|---|
toString() | Gibt ClassName@hashCodeHex zurück — fast nie das, was man möchte |
equals(Object) | Referenzgleichheit (==) |
hashCode() | Ein Wert, der von der Identität des Objekts abgeleitet wird, nicht von seinem Inhalt |
getClass() | Das Class<?>-Token zur Laufzeit — niemals null |
clone() | Eine flache feldweise Kopie, aber nur bei Klassen, die Cloneable implementieren; sonst wird eine Ausnahme geworfen |
wait(), notify(), notifyAll() | Low-Level-Monitor-Primitive, die mit synchronized verwendet werden |
finalize() | Hook, der vom GC vor dem Freigeben eines Objekts aufgerufen wird — veraltet, nicht verwenden |
Die ersten drei — toString, equals und hashCode — sollten fast jede werttragende Klasse überschreiben, da die geerbten Versionen die Identität eines Objekts statt seines Inhalts beschreiben. getClass ruft man auf Objekten auf, überschreibt es aber nie. Die Threading-Primitive berührt man selten direkt. clone und finalize sollten im modernen Code vermieden werden (clone bekommt ein eigenes Kapitel; finalize wird durch try-with-resources und Cleaner ersetzt).
record (Java 16+) verwenden, generiert der Compiler toString, equals und hashCode für Sie aus den Komponenten — alle drei basieren auf dem Inhalt, nicht auf der Identität. Das beseitigt den größten Teil des Boilerplate-Codes, um den es in diesem Kapitel geht. Siehe Java Records.toString — der Standard ist nicht hilfreich
Das geerbte toString gibt etwas wie Box@1540e19d zurück. Das ist der Klassenname und der Identitäts-Hashcode als Hex — nutzlos für Logging, Debugging oder jede Art von Diagnose:
Box b = new Box();
System.out.println(b); // Box@1540e19dÜberschreiben Sie es, um etwas Lesbares zurückzugeben — println, String-Verkettung, Logging-Frameworks und IDE-Debugger rufen alle toString für Sie auf, sodass eine gute Implementierung sich überall auszahlt:
@Override
public String toString() {
return "Box[contents=" + contents + "]";
}Das nächste Kapitel zeigt, wie man das gut macht.
equals und hashCode — sie gehören zusammen
Das geerbte equals gibt nur true zurück, wenn beide Referenzen auf dasselbe Objekt zeigen:
String a = new String("hi");
String b = new String("hi");
System.out.println(a == b); // false — different objects
System.out.println(a.equals(b)); // true — String overrides equalsString überschreibt equals, um den Inhalt zu vergleichen. Eine selbst geschriebene Klasse tut das nicht, außer Sie überschreiben es selbst. Und sobald Sie equals überschreiben, müssen Sie auch hashCode überschreiben — andernfalls verhalten sich Ihre Objekte in HashSet und HashMap still und heimlich falsch. Das Kapitel über equals und hashCode erläutert die genauen Regeln.
equals ohne hashCode überschreiben, können zwei Objekte, die Sie als gleich betrachten, in verschiedene Hash-Buckets landen — sodass ein HashSet fröhlich beide speichert und map.get(key) null für einen Schlüssel zurückgibt, der eindeutig vorhanden ist. Überschreiben Sie immer beide zusammen und halten Sie sie konsistent: gleiche Objekte müssen gleiche Hashcodes zurückgeben.getClass — das Laufzeittyp-Token
getClass() gibt das Class<?>-Objekt zurück, das den Laufzeittyp beschreibt:
Object o = "hello";
System.out.println(o.getClass().getName()); // java.lang.StringEs wird in reflektivem Code verwendet, in equals-Implementierungen, die exakte Typvergleiche benötigen, und in Debug-Ausgaben. Beachten Sie, dass getClass() final ist — Sie können es nicht überschreiben.
clone und Cloneable
Object.clone() ist protected und gelingt nur bei Klassen, die explizit implements Cloneable deklarieren. Das Ergebnis ist eine flache Kopie: eine neue Instanz mit denselben Feldwerten, einschließlich derselben Referenzen auf veränderliche Unterobjekte. Es ist eine bekanntermaßen unhandliche API, die im Cloning-Kapitel ausführlich behandelt wird.
wait / notify
Diese Methoden sowie notifyAll sind Teil von Javas ursprünglichen Low-Level-Nebenläufigkeitsprimitiven. Sie sind an synchronized-Blöcke gebunden und werden verwendet, um Warte-auf-Bedingung-Muster zu bauen. Im modernen Code bevorzugt man die höherwertigen Hilfsmittel in java.util.concurrent (Locks, BlockingQueue, CompletableFuture); rohes wait/notify ist außerhalb von Bibliotheks-Interna selten.
finalize — nicht anfassen
finalize() war einst die Möglichkeit des GC, Ihrem Objekt eine letzte Chance zur Freigabe von Ressourcen zu geben. Es ist seit Java 9 veraltet und in neueren Versionen entfernt. Verwenden Sie try-with-resources für I/O und java.lang.ref.Cleaner für den sehr seltenen Fall, dass nach der Garbage Collection Code ausgeführt werden muss.
Ein ausgearbeitetes Beispiel
Was kommt als Nächstes
Die häufigste — und fehlerträchtigste — Überschreibung einer Object-Methode ist equals zusammen mit hashCode. Das nächste Kapitel erläutert den Vertrag, den beide Methoden erfüllen müssen, und die Muster für eine korrekte Implementierung. Weiter zu Java equals und hashCode.