Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 6 Eigene Klassen schreiben
  gp 6.1 Eigene Klassen definieren
    gp 6.1.1 Methodenaufrufe und Nebeneffekte
    gp 6.1.2 Argumentübergabe mit Referenzen
    gp 6.1.3 Die this-Referenz
    gp 6.1.4 Überdeckte Objektvariablen nutzen
  gp 6.2 Assoziationen zwischen Objekten
  gp 6.3 Privatsphäre und Sichtbarkeit
    gp 6.3.1 Wieso nicht freie Methoden und Variablen für alle?
    gp 6.3.2 Privat ist nicht ganz privat. Es kommt darauf an, wer’s sieht
    gp 6.3.3 Zugriffsmethoden für Attribute definieren
    gp 6.3.4 Zusammenfassung zur Sichtbarkeit
    gp 6.3.5 Sichtbarkeit in der UML
  gp 6.4 Statische Methoden und Attribute
    gp 6.4.1 Warum statische Eigenschaften sinnvoll sind
    gp 6.4.2 Statische Eigenschaften mit static
    gp 6.4.3 Statische Eigenschaften als Objekteigenschaften nutzen
    gp 6.4.4 Statische Eigenschaften und Objekteigenschaften
    gp 6.4.5 Statische Variablen zum Datenaustausch
    gp 6.4.6 Warum die Groß- und Kleinschreibung wichtig ist
    gp 6.4.7 Statische Blöcke
  gp 6.5 Konstanten und Aufzählungen
    gp 6.5.1 Konstanten mit dem Schlüsselwort final bei Variablen
    gp 6.5.2 Problem mit finalen Klassenvariablen
    gp 6.5.3 Typsicherere Konstanten
    gp 6.5.4 Aufzählungen und enum in Java 5
    gp 6.5.5 enum-Konstanten in switch
  gp 6.6 Objekte anlegen und zerstören
    gp 6.6.1 Konstruktoren schreiben
    gp 6.6.2 Einen anderen Konstruktor der gleichen Klasse aufrufen
    gp 6.6.3 Initialisierung der Objekt- und Klassenvariablen
    gp 6.6.4 Finale Werte im Konstruktor setzen
    gp 6.6.5 Exemplarinitialisierer (Instanzinitialisierer)
    gp 6.6.6 Zerstörung eines Objekts durch den Müllaufsammler
    gp 6.6.7 Implizit erzeugte String-Objekte
    gp 6.6.8 Private Konstruktoren, Utility-Klassen, Singleton und Fabriken
    gp 6.6.9 Zusammenfassung: Konstruktoren und Methoden
  gp 6.7 Vererbung
    gp 6.7.1 Vererbung in Java
    gp 6.7.2 Einfach- und Mehrfachvererbung
    gp 6.7.3 Gebäude modelliert
    gp 6.7.4 Konstruktoren in der Vererbung
    gp 6.7.5 Sichtbarkeit
    gp 6.7.6 Das Substitutionsprinzip
    gp 6.7.7 Automatische und explizite Typanpassung
    gp 6.7.8 Array-Typen
    gp 6.7.9 Finale Klassen
    gp 6.7.10 Unterklassen prüfen mit dem Operator instanceof
    gp 6.7.11 Methoden überschreiben
    gp 6.7.12 super: Aufrufen einer Methode aus der Oberklasse
    gp 6.7.13 Nicht überschreibbare Funktionen
  gp 6.8 Die oberste aller Klassen: Object
    gp 6.8.1 Klassenobjekte
    gp 6.8.2 Objektidentifikation mit toString()
    gp 6.8.3 Objektgleichheit mit equals() und Identität
    gp 6.8.4 Klonen eines Objekts mit clone()
    gp 6.8.5 Hashcodes
    gp 6.8.6 Aufräumen mit finalize()
    gp 6.8.7 Synchronisation
  gp 6.9 Die Oberklasse gibt Funktionalität vor
    gp 6.9.1 Dynamisches Binden als Beispiel für Polymorphie
    gp 6.9.2 Keine Polymorphie bei privaten, statischen und finalen Methoden
    gp 6.9.3 Polymorphie bei Konstruktoraufrufen
  gp 6.10 Abstrakte Klassen
    gp 6.10.1 Abstrakte Klassen
    gp 6.10.2 Abstrakte Methoden
    gp 6.10.3 Über abstract final
  gp 6.11 Schnittstellen
    gp 6.11.1 Ein Polymorphie-Beispiel mit Schnittstellen
    gp 6.11.2 Die Mehrfachvererbung bei Schnittstellen
    gp 6.11.3 Erweitern von Interfaces – Subinterfaces
    gp 6.11.4 Vererbte Konstanten bei Schnittstellen
    gp 6.11.5 Vordefinierte Methoden einer Schnittstelle
    gp 6.11.6 CharSequence als Beispiel einer Schnittstelle
    gp 6.11.7 Die Schnittstelle Iterable
  gp 6.12 Innere Klassen
    gp 6.12.1 Statische innere Klassen und Schnittstellen
    gp 6.12.2 Mitglieds- oder Elementklassen
    gp 6.12.3 Lokale Klassen
    gp 6.12.4 Anonyme innere Klassen
    gp 6.12.5 Eine Sich-Selbst-Implementierung
    gp 6.12.6 this und Vererbung
    gp 6.12.7 Implementierung einer verketteten Liste
    gp 6.12.8 Funktionszeiger
  gp 6.13 Generische Datentypen
    gp 6.13.1 Einfache Klassenschablonen
    gp 6.13.2 Einfache Methodenschablonen
    gp 6.13.3 Generics und Vererbung
    gp 6.13.4 Einschränken der Typen
    gp 6.13.5 Umsetzen der Generics, Typlöschung und Raw-Types
    gp 6.13.6 Wildcards
  gp 6.14 Die Spezial-Oberklasse Enum
    gp 6.14.1 Methoden auf Enum-Objekten
    gp 6.14.2 enum mit eigenen Konstruktoren und Methoden
  gp 6.15 Gegenseitige Abhängigkeiten von Klassen
  gp 6.16 Veraltete (deprecated) Methoden/Konstruktoren


Galileo Computing

6.8 Die oberste aller Klassen: Object  downtop

Wie schon an anderer Stelle betont, ist Object – definiert in der Klassendatei java.lang.Object – die oberste aller Klassen. Somit spielt diese Klasse eine ganz besondere Rolle, da alle anderen Klassen automatisch Unterklassen sind und die Methoden erben beziehungsweise überschreiben.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

6.8.1 Klassenobjekte  downtop

Zwar ist jedes Objekt ein Exemplar einer Klasse – doch was ist eine Klasse? In Sprachen wie C++ existieren keine Klassen zur Laufzeit, und der Compiler übersetzt die Klassenstruktur in ein Programm. Im absoluten Gegensatz dazu steht Smalltalk: Diese Laufzeitumgebung verwaltet alle Klassen als Objekte. Die speziellen Klassenobjekte bilden dann den Bauplan für neue Exemplare.

Die Klasse Object bietet eine spezielle Methode getClass() an, die eine Referenz auf das Klassenobjekt vom Typ java.lang.Class zurückgibt, die das Objekt konstruiert hat. Der Vorteil liegt klar auf der Hand: Wenn Klassen als Objekte vorliegen, dann können sie auch über das Netz geladen und ausgeführt werden. Ein Nachteil ist die verminderte Geschwindigkeit. Doch das wird gerne in Kauf genommen.


Beispiel   Die Objektmethode der Klasse Class.getName() fragt nach dem Namen der Objektklasse. Die folgende Zeile ergibt die Ausgabe java.lang.String:

System.out.println( "Klaviklack".getClass().getName() );

Klassen-Literale

Ein Klassen-Literal (eng. class literal) ist ein Ausdruck der Form Datentyp.class, wobei Datentyp entweder eine Klasse, eine Schnittstelle, ein Feld oder ein primitiver Typ ist. Der Ausdruck ist immer vom Typ Class. Beispiele sind Object.class oder int.class.


Galileo Computing

6.8.2 Objektidentifikation mit toString(downtop

Jedes Objekt sollte sich durch die Methode toString() mit einer Zeichenkette identifizieren und den Inhalt der interessanten Attribute als Zeichenkette liefern.


Beispiel   Die Klasse Point definiert toString() so, dass die Koordinaten angegeben werden.

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 Argument aufgerufen werden. Ähnliches gilt für den Zeichenkettenoperator + mit einer Objektreferenz als Operand.

Listing 6.36   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.


Galileo Computing

6.8.3 Objektgleichheit mit equals() und Identität  downtop

Ob zwei Referenzen auf das gleiche Objekt zeigen, lässt sich durch den Vergleichsoperator = = feststellen. Damit wird aber lediglich die Identität, nicht jedoch automatisch die inhaltliche Gleichheit getestet. So wird der Vergleich


if ( meinName == "Ulli" )

gewiss einen falschen, unbeabsichtigten Effekt haben, obwohl er syntaktisch korrekt ist. An dieser Stelle sollte der inhaltliche Vergleich stattfinden: Stimmen alle Zeichen der Zeichenkette überein?

Mit der Methode equals() aus Object lassen sich Objekte auf Gleichheit prüfen. Unterklassen überschreiben diese Methode, um einen inhaltlichen Vergleich vorzunehmen. Die Methode ist in jeder Klasse auch gut aufgehoben, denn nur ein Objekt weiß, wann es gleich einem anderen ist. So besitzt das String-Objekt eine Implementierung, die jedes Zeichen vergleicht:


String meinName = "Ulli";

if ( meinName.equals( "Ulli" ) )
  // gefunden

Leider implementiert nicht jede Klasse eine eigene equals()-Methode, so dass die Laufzeitumgebung unbeabsichtigt bei Object und seinem Referenzenvergleich landet. Diese Fehleinschätzung kommt leider bei Exemplaren der Klasse StringBuffer vor, die kein eigenes equals() implementiert. Wir haben die Diskussion darüber schon geführt.

Ein weiterer Aspekt von equals() ist Folgender: Das Ergebnis muss über die gesamte Lebensdauer eines Objekts immer gleich bleiben. Ein kleines Problem steckt dabei in equals() der Klasse URL, die vergleicht, ob zwei URL-Adressen auf die gleiche Ressource zeigen. Der Vergleich vertraut auf die IP-Adresse des Rechners, was jedoch sehr problematisch ist. Gerade die Abstraktion von veränderbaren IP-Adressen mit ihren dynamischen Auflösungen machen URLs attraktiv. Eine IP-Adresse eines Rechners kann sich ändern, auch wenn sich die URL nicht ändert. Das heißt, ändert sich die IP-Adresse des Rechners, ist auf einmal ein URL-Objekt nicht mehr gleich sich selbst, obwohl die URL unverändert bleibt. Den Fehler sollten wir bei unseren Programmen nicht machen. Wenn der Test auf Gleichheit nicht möglich ist, müssen wir einen Test definieren, der möglich ist (etwa Vergleich der URL-Daten, wie Rechnername und Datei), oder auf den Test verzichten und beim Vergleich der Referenzen bleiben.

Ein eigenes equals()

Bei selbstdefinierten Methoden ist Vorsicht geboten, denn wir müssen genau auf die Signatur achten. Die Methode muss ein Object akzeptieren und boolean zurückgeben. Wird diese Signatur falsch verwendet, so kommt es an Stelle einer Überschreibung der Funktion zu einer Überladung. Dies hat ungeahnte Folgen, denn dann wird einfach die Standardimplementierung aufgerufen. Diese kann über die Gleichheit von Objekten nichts wissen und testet lediglich die Referenzen.


public boolean equals( Object obj )
{
  return (this == obj);
}

Die equals()-Funktion stellt einige Anforderungen:

gp  Heißt der Vergleich equals(null), so ist das Ergebnis immer false.
gp  Kommt ein this rein, lässt sich eine Abkürzung nehmen und true zurückliefern.
gp  Das Argument ist zwar vom Typ Object, aber dennoch vergleichen wir immer konkrete Typen. Eine equals()-Funktion einer Klasse X wird sich daher nur mit Objekten vom Typ X vergleichen lassen.

Die ersten beiden Punkt sind leicht erfüllt. Nehmen wir dazu ein Beispiel einer Klasse DiskoEquals.

Listing 6.37   DiskoEquals.java, erster Versuch


public class DiskoEquals
{
  int anzahlPersonen;
  int quadratmeter;

  public boolean equals( Object o )
  {
    if ( o == null )
      return false;

    if ( o == this )
      return true;

    DiskoEquals that = (DiskoEquals) o;

    return    this.anzahlPersonen == that.anzahlPersonen
           && this.quadratmeter   == that.quadratmeter;
  }
}

Diese Lösung erscheint offensichtlich, führt aber spätestens bei einem nicht-DiskoEquals-Objekt zu einer ClassCastException. Das ist schnell behoben:


if ( ! o instanceof DiskoEquals )
  return false;

Jetzt sehen wir uns auf der sicheren Seite, aber das Ziel ist noch nicht ganz erreicht. Zwar funktioniert die aufgeführte Implementierung bei finalen Klassen schön, aber bei Unterklassen ist die Symmetrie gebrochen. Warum? Ganz einfach: instanceof testet Typen in der Hierarchie, liefert also auch dann true, wenn das an equals() übergebene Argument eine Unterklasse von DiskoEquals ist. Diese Unterklasse wird wie die Oberklasse die gleichen Attribute haben, so dass – aus Sicht von DiskoEquals – alles in Ordnung ist. Nehmen wir im Moment einmal die Variablen disko und superDisko an, die die Typen DiskoEquals und SuperDiskoEquals – die Unterklasse von DiskoEquals – besitzen. Sind beide Objekte gleich, so ergibt disko.equals(superDisko) das Ergebnis true. Drehen wir den Spieß nun um, und fragen, was superDisko.equals(disko) ergibt. Zwar haben wir SuperDiskoEquals nicht implementiert, aber nehmen wir an, dass dort eine equals()-Funktion steckt, die nach dem gleichen instanceof-Schema implementiert wurde wie DiskoEquals. Dann wird dort bei einem Test ausgeführt: disko instanceof superDisko und das ist false. Damit wird aber die Fallunterscheidung mit return false beendet. Fassen wir zusammen:


disko.equals( superDisko ) == true
superDisko.equals( disko ) == false

Das darf nicht sein und zur Lösung dürfen wir nicht instanceof verwenden, sondern müssen fragen, ob der Typ exakt ist. Das geht mit getClass(). Korrekt ist daher folgendes:

Listing 6.38   DiskoEquals.java, Korrektur


public class DiskoEquals
{
  int anzahlPersonen;
  int quadratmeter;

  public boolean equals( Object o )
  {
    if ( o == null )
      return false;

    if ( o == this )
      return true;

      if ( ! o.getClass().equals(getClass()) )  
        return false;  

    DiskoEquals that = (DiskoEquals) o;

    return    this.anzahlPersonen == that.anzahlPersonen
      && this.quadratmeter   == that.quadratmeter;
}
}

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.


Galileo Computing

6.8.4 Klonen eines Objekts mit clone(downtop

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.
       
2. Eine Funktion clone() so schreiben, dass Java-System veranlasst wird, eine Kopie zu erstellen.
       

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:

gp  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.
gp  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();
    }
  }
}

Abbildung
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() und equals()

Die Methode clone() und die Methode equals() hängen wie auch equals() und hashCode() zusammen. Wenn die clone()-Methode überschrieben wird, sollte auch equals() angepasst werden, denn ohne ein überschriebenes equals() bleibt in Object stehen:


public boolean equals( Object obj )
{
  return (this == obj);
}

Das heißt aber, dass ein geklones Objekt – welches ja im Allgemeinen ein neues Objekt ist – durch seine neue Objektidentität nicht mehr equals() zu seinem Erzeuger ist. Formal: o.clone().equals(o) == false. Diese Semantik dürfte nicht erwünscht sein.

Flach oder tief?

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.

Kovariante Rückgabewerte

Bisher sieht die Java-Sprachdefinition nicht vor, dass es mehrere Methoden in einer Klasse geben kann, die sich nur in ihren Rückgabetypen unterscheiden. Das wären kovariante Rückgabewerte. Ebenfalls ist es nicht möglich, in einer Unterklasse eine Methode zu überschreiben, die einen anderen Rückgabetyp besitzt. Die Methode würde nicht überschrieben, und es gibt einen Compilerfehler, da ererbte und neue Methode gegen die Regel verstoßen, dass zwei Methoden sich nie nur im Rückgabetyp unterscheiden dürfen.

Im Design von großen Programmen wäre es jedoch günstig, wenn ein nichtprimitiver Rückgabetyp einer überschriebenen Funktion ebenfalls vom Typ dieser Unterklasse ist. Auf diese Weise ließen sich korrekt angepasste, überschriebene Methoden implementieren, und Entwickler könnten sich die expliziten Typanpassungen ersparen.

Betrachten wir zum Beispiel die clone()-Methode aus Object. Sie trägt ausdrücklich den Rückgabetyp Object. Auch eine Unterklasse muss den Rückgabetyp Object deklarieren, daher ist Folgendes leider falsch:

class X        // automatisch extends Object
{
  public X clone()
  {
    return (X)super.clone();
  }
}

Der Aufrufer der Methode clone() von X muss selbstständig eine Typumwandlung auf X vornehmen.

Das Komische in diesem Zusammenhang ist, dass es schon veränderte Zugriffsrechte gibt. Eine Unterklasse kann die Sichtbarkeit erweitern. Auch bei Ausnahmen kann eine Unterklasse speziellere Ausnahmen beziehungsweise ganz andere als die Methode der Oberklasse erzeugen.

Die aktuelle Sprachdefinition von C++ unterstützt kovariante Rückgabewerte. Im Zuge der kommenden Version 5 werden auch kovariante Rückgabewerte in die Sprache Einzug erhalten. Auch einige Java-Erweiterungen, unter ihnen der freie GJ-Compiler, erlauben kovariante Rückgabewerte ohne Änderung der JVM durch Einfügen von Typanpassungen.


Galileo Computing

6.8.5 Hashcodes  downtop

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.39   DiskoHashing.java


public class DiskoHashing
{
  int anzahlPersonen;
  int quadratmeter;

  /**
   * Liefert den Hashcode für das aktuelle <code>DiskoHashing</code>-Objekt.
   *
   * @return Hashcode.
   */
  public int hashCode()
  {
    return anzahlPersonen ^ ( quadratmeter >> 16 );
  }

  public static void main( String[] args )
  {
    DiskoHashing d = new DiskoHashing();
    d.anzahlPersonen = 1223;
    d.quadratmeter = 633;

    System.out.println( d.hashCode() );   // 1726
  }
}

Equals, die Null und Hashen

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.


Galileo Computing

6.8.6 Aufräumen mit finalize()  downtop

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();
  }
}


Galileo Computing

6.8.7 Synchronisation  toptop

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.





Copyright © 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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de