![]() |
|
|||||
Neue Klassen sollten diese Methode überschreiben. Wenn das nicht der Fall ist, gelangt das Programm zur Standardimplementierung in Object: public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } Dann wird lediglich der Klassenname und der nichts sagende Hash-Wert hexadezimal ausgeben. Das Angenehme ist, dass toString() automatisch aufgerufen wird, wenn die Methoden print() oder println() mit einer Objektreferenz als Parameter aufgerufen werden. Ähnliches gilt für den Zeichenkettenoperator + mit einer Objektreferenz als Operand. Listing 6.35 DiskoAusgabe.java public class DiskoAusgabe { int anzahlPersonen; int quadratmeter; public String toString() { return getClass().getName() + "[anzahlPersonen=" + anzahlPersonen + ",quadratmeter=" + quadratmeter + "]"; } public static void main(String[] args) { DiskoAusgabe d = new DiskoAusgabe(); d.anzahlPersonen = 1223; // DiskoAusgabe[anzahlPersonen=1223,quadratmeter=633] d.quadratmeter = 633; // :DiskoAusgabe[anzahlPersonen=1223,quadratmeter=633]: System.out.println( d ); System.out.println( ":" + d + ":"); } } Bei einer eigenen Implementierung müssen wir darauf achten, dass die Sichtbarkeit public ist, da sich toString() in der Oberklasse public befindet und wir die Sichtbarkeit nicht einschränken können. 6.9.3 Objektgleichheit mit equals() und Identität
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Beispiel Eine korrekte Implementierung der Methode equals() für eine Klasse X |
Das Beispiel ist allgemein gehalten und zeigt, was für fast alle equals()-Methoden gilt. Wir wollen zudem unterscheiden, ob EqualsDemo eine direkte Unterklasse von Object oder Unterklasse einer weiteren Klasse O ist.
Listing 6.36 EqualsDemo.java
class EqualsDemo // extends O { public boolean equals( Object o ) { if ( o == null ) return false; if ( o == this ) return true; // Fall 1: // X ist direkte Unterklasse von Object if ( getClass() != o.getClass() ) return false; // testet Gleichheit der Klassen einmal direkt unterhalb der // Klasse Object. // Fall 2: // X ist Unterklasse von O if ( !super.equals(o) ) return false; // Attribute der Oberklasse (und Klassenzugehörigkeit) // verglichen. return equals( (X)o ); } public boolean equals( X o ) // Sichtbarkeit nach Wunsch. { // Vergleich, ob alle Attribute vom eigenen // Objekt, mit denen von o identisch sind. // Dann return true, sonst return false. }
Es ist günstig, bei erweiterten Klassen ein neues equals() anzugeben, so dass auch die neuen Attribute in den Test einbezogen werden. Bei hashCode()-Methoden müssen wir eine ähnliche Strategie anwenden, was wir hier nicht zeigen wollen.
Zum Replizieren eines Objekts lässt sich eine vordefinierte Methode einsetzen: clone(). Der Aufruf soll eine Kopie des Objekts liefern.
Beispiel Erzeuge ein Punkt-Objekt und klone es:
java.awt.Point p = new java.awt.Point(12, 23); java.awt.Point q = (java.awt.Point) p.clone(); System.out.println( q ); // java.awt.Point[x=12,y=23] |
Viele der Standard-Klassen unterstützen ein clone(), so dass ein neues Exemplar mit dem gleichen Zustand zurückgegeben wird. Für uns als Programmierer stellt sich die Frage, wie ein clone() unserer Klassen mit geringem Aufwand umgesetzt wird. Zwei Möglichkeiten kommen in betracht:
| 1. | Die Funktion clone() schreiben, dort von Hand ein neues Objekt anlegen, alle Attribute kopieren und die Referenz auf das neue Objekt zurückgeben. |
Lösung zwei verkürzt die Entwicklungszeit und ist auch spannender. Denn um das System zum Klonen zu bewegen, müssen zwei Dinge getan werden:
| Die Funktion public Object clone() muss überschrieben werden. Weiterhin muss die Funktion clone() der Oberklasse mit super.clone() aufgerufen werden. Dies wird oft die Funktion clone() aus Object sein. Sie selbst ist protected, aber das ist der Trick: Nur Unterklassen können clone() aufrufen, keiner sonst. |
| Es muss die Markierungsschnittstelle Cloneable implementiert werden. Falls von außen ein clone() auf einem Objekt aufgerufen wird, dessen Klasse nicht Cloneable implementiert, ist das Ergebnis eine CloneNotSupportedException. |
Nach dem Aufrufen der Oberklassenfunktion mit super.clone() erzeugt das Laufzeitsystem ein neues Exemplar der Klasse und kopiert elementweise die Daten des aktuellen Objekts in das neue. Jede Klasse bestimmt jedoch eigenständig, welche Attribute kopiert werden. Die Methode gibt eine Referenz auf das neue Objekt zurück. Die Funktion kann einen OutOfMemoryError liefert, wenn es keinen freien Speicher mehr gibt.
| Beispiel In einer Disko arbeitet eine Thekenbedienung. Wir wünschen uns, dass wir die Mitarbeiter leicht klonen können. |
public class Bedienung implements Cloneable { String name; int alter; public Object clone() { try { return super.clone(); } catch ( CloneNotSupportedException e ) { // this shouldn't happen, since we are Cloneable |
throw new InternalError();
}
}
}
|

Hier klicken, um das Bild zu Vergrößern
Testen wir die Klasse etwa so:
Bedienung susi = new Bedienung(); susi.name = "Susi"; Bedienung dolly = (Bedienung) susi.clone(); System.out.println( dolly.name ); // Susi
clone() erzeugt standardmäßig nur flache Kopien. Bei untergeordneten Objekten werden nur die Referenzen kopiert und Originalobjekt sowie Kopie verweisen anschließend auf dieselben untergeordneten Objekte (verwenden diese gemeinsam). Wenn zum Beispiel die Bedienung ein Attribut für einen Arbeitgeber besitzt und eine Kopie der Bedienung erzeugt wird, so wird der Klon auf den gleichen Arbeitgeber zeigen. Bei einem Arbeitgeber mag das noch stimmig sein, aber bei Datenstrukturen sind mitunter tiefe Kopien gewünscht.
Die Methode hashCode() soll zu jedem Objekt eine möglichst eindeutige Integerzahl (sowohl positiv als auch negativ) liefern, die das Objekt identifiziert. Inhaltlich gleiche Objekte (gemäß der Methode equals()) müssen denselben Wert bekommen. Eine spezielle Funktion berechnet diesen Wert, der Hashcode oder Hash-Wert genannt wird. Die Funktionen, die solche Werte berechnen, nennen sich Hash-Funktionen.
Hashcodes werden verwendet, um Elemente in Hash-Tabellen zu speichern. Diese sind Datenstrukturen, die einen effizienten Zugriff auf ihre Elemente erlauben. Die Klassen java.util.HashMap oder java.util.Hashtable implementieren eine solche Datenstruktur.
Listing 6.37 DiskoHashing.java
public class DiskoHashing { int anzahlPersonen; int quadratmeter; /** * Liefert den Hashcode für das aktuelle DiskoHashing-Objekt. * * @return Hashcode. */ public int hashCode() { return anzahlPersonen ^ ( quadratmeter >> 32 ); } public static void main( String[] args ) { DiskoHashing d = new DiskoHashing(); d.anzahlPersonen = 1223; d.quadratmeter = 633; System.out.println( d.hashCode() ); // 1726 } }
Die beiden Methoden hashCode() und equals() hängen zusammen, so dass in der Regel bei der Implementierung einer Funktion auch eine Implementierung der anderen notwendig wird. Denn es gilt, dass bei Gleichheit natürlich auch die Hash-Werte übereinstimmen müssen. Formal gesehen heißt das:
x.equals( y ) Û x.hashCode() == y.hashCode()
So berechnet sich der Hashcode bei Point-Objekten aus den Koordinaten. Zwei Punkt-Objekte, die inhaltlich gleich sind, haben die gleichen Koordinaten und damit auch den gleichen Hashcode.
Eine spezielle Methode finalize() wird immer dann aufgerufen, wenn der GC ein Objekt entfernen möchte. Objekte sollten diese Funktion überschreiben, wenn sie beispielsweise noch Dateien schließen müssen. Achtung! Wenn noch genügend Speicherplatz vorhanden ist, wird womöglich der GC nie aufgerufen.
Überschreiben wir in einer Unterklasse diese Methode, dann müssen wir auch gewährleisten, dass finalize() der Oberklasse aufgerufen wird. Das erreichen wir mit der Referenz super. (Es wäre gut, wenn der Compiler das automatisch machen würde ...)
| Beispiel Wir bilden eine Unterklasse von Font, um unsere eigenen Zeichensätze zu verwalten. Die Klasse Font definiert eine finalize()-Methode, und unsere Klasse soll auch finalize() implementieren: |
class MehrAlsFontKann extends Font { MehrAlsFontKann () // Font hat keinen Standard-Konstruktor { super( null, PLAIN, 10 ); } protected void finalize() throws Throwable { /* MehrAlsFontKann Dinge freigeben ... */ super.finalize(); } } |
Threads können miteinander kommunizieren und dabei Daten teilen. Sie können außerdem auf das Eintreten bestimmter Bedingungen warten, zum Beispiel auf neue Eingabedaten. Die Klasse Object definiert insgesamt fünf Versionen der Methoden wait(), notify() und notifyAll() zur Beendigungssynchronisation von Threads. Ein Sonderkapitel geht näher auf die Programmierung von Threads ein.
1 Die Mathematiker werden sich freuen, denn die Methode equals() bildet eine Äquivalenzrelation. Sie ist, wenn wir die null-Referenz außen vor lassen, reflexiv, symmetrisch und transitiv.
| << zurück |
Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.