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

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Die Funktionsbibliothek
10 Threads und nebenläufige Programmierung
11 Raum und Zeit
12 Datenstrukturen und Algorithmen
13 Dateien und Datenströme
14 Die eXtensible Markup Language (XML)
15 Grafische Oberflächen mit Swing
16 Grafikprogrammierung
17 Netzwerkprogrammierung
18 Verteilte Programmierung mit RMI und Web-Services
19 JavaServer Pages und Servlets
20 Applets
21 Midlets und die Java ME
22 Datenbankmanagement mit JDBC
23 Reflection und Annotationen
24 Logging und Monitoring
25 Sicherheitskonzepte
26 Java Native Interface (JNI)
27 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort

Download:
- ZIP, ca. 12,5 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel

Java ist auch eine Insel
7., aktualisierte Auflage
geb., mit DVD (November 2007)
1.492 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1146-8
Pfeil 6 Eigene Klassen schreiben
Pfeil 6.1 Eigene Klassen mit Eigenschaften deklarieren
Pfeil 6.1.1 Attribute deklarieren
Pfeil 6.1.2 Methoden deklarieren
Pfeil 6.1.3 Die this-Referenz
Pfeil 6.2 Privatsphäre und Sichtbarkeit
Pfeil 6.2.1 Für die Öffentlichkeit: public
Pfeil 6.2.2 Paketsichtbar
Pfeil 6.2.3 Kein Public Viewing – Passwörter sind privat
Pfeil 6.2.4 Wieso nicht freie Methoden und Variablen für alle?
Pfeil 6.2.5 Privat ist nicht ganz privat: Es kommt darauf an, wer’s sieht
Pfeil 6.2.6 Zugriffsmethoden für Attribute deklarieren
Pfeil 6.2.7 Setter und Getter nach der JavaBeans-Spezifikation
Pfeil 6.3 Statische Methoden und statische Attribute
Pfeil 6.3.1 Warum statische Eigenschaften sinnvoll sind
Pfeil 6.3.2 Statische Eigenschaften mit static
Pfeil 6.3.3 Statische Eigenschaften über Referenzen nutzen?
Pfeil 6.3.4 Warum die Groß- und Kleinschreibung wichtig ist
Pfeil 6.3.5 Statische Eigenschaften und Objekteigenschaften
Pfeil 6.3.6 Statische Variablen zum Datenaustausch
Pfeil 6.3.7 Statische Blöcke als Klasseninitialisierer
Pfeil 6.4 Konstanten und Aufzählungen
Pfeil 6.4.1 Konstanten über öffentliche statische finale Variablen
Pfeil 6.4.2 Eincompilierte Belegungen der Klassenvariablen
Pfeil 6.4.3 Typ(un)sicherere Aufzählungen
Pfeil 6.4.4 Aufzählungen mit enum
Pfeil 6.5 Objekte anlegen und zerstören
Pfeil 6.5.1 Konstruktoren schreiben
Pfeil 6.5.2 Der Standard-Konstruktor
Pfeil 6.5.3 Parametrisierte und überladene Konstruktoren
Pfeil 6.5.4 Konstruktor nimmt ein Objekt vom eigenen Typ an (Copy-Konstruktor)
Pfeil 6.5.5 Einen anderen Konstruktor der gleichen Klasse aufrufen
Pfeil 6.5.6 Initialisierung der Objekt- und Klassenvariablen
Pfeil 6.5.7 Finale Werte im Konstruktor und in statischen Blöcken setzen
Pfeil 6.5.8 Exemplarinitialisierer (Instanzinitialisierer)
Pfeil 6.5.9 Ihr fehlt uns nicht – der Garbage-Collector
Pfeil 6.5.10 Implizit erzeugte String-Objekte
Pfeil 6.5.11 Private Konstruktoren, Utility-Klassen, Singleton, Fabriken
Pfeil 6.6 Assoziationen zwischen Objekten
Pfeil 6.6.1 Unidirektionale 1:1-Beziehung
Pfeil 6.6.2 Bidirektionale 1:1-Beziehungen
Pfeil 6.6.3 Unidirektionale 1:n-Beziehung
Pfeil 6.7 Vererbung
Pfeil 6.7.1 Vererbung in Java
Pfeil 6.7.2 Spielobjekte modelliert
Pfeil 6.7.3 Einfach- und Mehrfachvererbung
Pfeil 6.7.4 Sichtbarkeit protected
Pfeil 6.7.5 Konstruktoren in der Vererbung und super
Pfeil 6.7.6 Automatische und explizite Typanpassung
Pfeil 6.7.7 Das Substitutionsprinzip
Pfeil 6.7.8 Typen mit dem binären Operator instanceof testen
Pfeil 6.7.9 Methoden überschreiben
Pfeil 6.7.10 Mit super an die Eltern
Pfeil 6.7.11 Kovariante Rückgabetypen
Pfeil 6.7.12 Array-Typen und Kovarianz
Pfeil 6.7.13 Zusammenfassung zur Sichtbarkeit
Pfeil 6.8 Dynamisches Binden
Pfeil 6.8.1 Unpolymorph bei privaten, statischen und finalen Methoden
Pfeil 6.8.2 Polymorphie bei Konstruktoraufrufen
Pfeil 6.8.3 Finale Klassen
Pfeil 6.8.4 Nicht überschreibbare (finale) Methoden
Pfeil 6.9 Abstrakte Klassen und abstrakte Methoden
Pfeil 6.9.1 Abstrakte Klassen
Pfeil 6.9.2 Abstrakte Methoden
Pfeil 6.10 Schnittstellen
Pfeil 6.10.1 Deklarieren von Schnittstellen
Pfeil 6.10.2 Implementieren von Schnittstellen
Pfeil 6.10.3 Markierungsschnittstellen
Pfeil 6.10.4 Ein Polymorphie-Beispiel mit Schnittstellen
Pfeil 6.10.5 Die Mehrfachvererbung bei Schnittstellen
Pfeil 6.10.6 Keine Kollisionsgefahr bei Mehrfachvererbung
Pfeil 6.10.7 Erweitern von Interfaces – Subinterfaces
Pfeil 6.10.8 Vererbte Konstanten bei Schnittstellen
Pfeil 6.10.9 Schnittstellenmethoden, die nicht implementiert werden müssen
Pfeil 6.10.10 Abstrakte Klassen und Schnittstellen im Vergleich
Pfeil 6.11 Geschachtelte (innere) Klassen, Schnittstellen, Aufzählungen
Pfeil 6.11.1 Statische innere Klassen und Schnittstellen
Pfeil 6.11.2 Mitglieds- oder Elementklassen
Pfeil 6.11.3 Lokale Klassen
Pfeil 6.11.4 Anonyme innere Klassen
Pfeil 6.11.5 this und Vererbung
Pfeil 6.12 Generische Datentypen
Pfeil 6.12.1 Einfache Klassenschablonen
Pfeil 6.12.2 Einfache Methodenschablonen
Pfeil 6.12.3 Umsetzen der Generics, Typlöschung und Raw-Types
Pfeil 6.12.4 Einschränken der Typen
Pfeil 6.12.5 Generics und Vererbung, Invarianz
Pfeil 6.12.6 Wildcards
Pfeil 6.13 Die Spezial-Oberklasse Enum
Pfeil 6.13.1 Methoden auf Enum-Objekten
Pfeil 6.13.2 enum mit eigenen Konstruktoren und Methoden
Pfeil 6.14 Dokumentationskommentare mit JavaDoc
Pfeil 6.14.1 Einen Dokumentationskommentar setzen
Pfeil 6.14.2 Mit javadoc eine Dokumentation erstellen
Pfeil 6.14.3 HTML-Tags in Dokumentationskommentaren
Pfeil 6.14.4 Generierte Dateien
Pfeil 6.14.5 Dokumentationskommentare im Überblick
Pfeil 6.14.6 JavaDoc und Doclets
Pfeil 6.14.7 Veraltete (deprecated) Klassen, Konstruktoren und Methoden


Galileo Computing - Zum Seitenanfang

6.11 Geschachtelte (innere) Klassen, Schnittstellen, Aufzählungen Zur nächsten ÜberschriftZur vorigen Überschrift

Bisher haben wir Klassen, Schnittstellen und Aufzählungen kennengelernt, die entweder alleine in der Datei oder zusammen mit anderen Typen in einer Datei deklariert wurden. Es gibt darüber hinaus die Möglichkeit, eine Klasse, Aufzählung oder Schnittstelle in andere Typdeklarationen hineinzunehmen. Für eine Klasse In, die in eine Klasse Out gesetzt wird, sieht das zum Beispiel so aus:

class Out { 
  class In { 
  } 
}

Eine geschachtelte Klasse, die so eingebunden wird, heißt »innere Klasse«. Im Folgenden wollen wir nicht mehr ständig betonen, dass auch Schnittstellen als Typen eingebettet werden können und bleiben bei der einfachen Sprachweise »innere Klassen«. (Aufzählungen werden vom Compiler zu Klassen und müssen daher nicht unbedingt gesondert behandelt werden.)

Die Java-Spezifikation beschreibt vier Typen von inneren Klassen, die im Folgenden beschrieben werden. Egal, wie sie deklariert werden, es ist eine enge Kopplung der Typen, und der Name des inneren Typs muss sich vom Namen des äußeren Typs unterscheiden.


Statische innere Klasse

class Out 
{ 
 static class In {} 
}

Mitgliedsklasse

class Out 
{ 
 class In { } 
}

Lokale Klasse

class Out 
{ 
 Out() 
 { 
  class In { } 
 } 
}

Anonyme innere Klasse

new Runnable() { 
  public void run() { } 
};


Hinweis Hinweis Das Gegenteil von geschachtelten Klassen, also das, womit wir uns bisher die die ganze Zeit beschäftigt haben, heißt »Top-Level-Klasse«. Normale Top-Level-Typen können paketsichtbar oder public sein; innere Klassen dürfen public, paketsichtbar, protected und private sein.



Galileo Computing - Zum Seitenanfang

6.11.1 Statische innere Klassen und Schnittstellen Zur nächsten ÜberschriftZur vorigen Überschrift

Die einfachste Variante einer inneren Klasse oder Schnittstelle wird wie eine statische Eigenschaft in die Klasse eingesetzt und heißt statische innere Klasse. Wegen der Schachtelung wird dieser Typ im Englischen nested top-level class genannt. Die Namensgebung betont mit dem Begriff top-level, dass die Klassen das Gleiche können wie »normale« Klassen oder Schnittstellen, nur bilden sie quasi ein kleines Unterpaket mit eigenem Namensraum. Insbesondere sind zur Erzeugung von Exemplaren von statischen inneren Klassen nach diesem Muster keine Objekte der äußeren Klasse nötig. (Die weiteren inneren Typen, die wir kennenlernen wollen, sind alle nicht statisch und benötigen einen Verweis auf das äußere Objekt.) Sun betont in der Spezifikation der Sprache, dass die statischen inneren Klassen keine »echten« inneren Klassen sind, doch um die Sprache einfach zu halten, bleiben wir bei »statischen inneren Typen«.

Deklarieren wir Lamp als äußere Klasse und Bulb als eine innere statische Klasse.

Listing 6.92 com.tutego.insel.inner.Lamp.java, Lamp

public class Lamp 
{ 
  static String s = "Huhu"; 
  int i = 1; 
 
  static class Bulb 
  { 
    void out() 
    { 
      System.out.println( s ); 
//      System.out.println( i );          // Fehler, da i nicht statisch 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    Bulb bulb = new Lamp.Bulb();  // oder Lamp.Bulb bulb = ... 
    bulb.out(); 
  } 
}

Die statische innere Klasse Bulb besitzt Zugriff auf alle anderen statischen Eigenschaften der äußeren Klasse Lamp, in unserem Fall die Variable s. Ein Zugriff auf Objektvariablen ist aus der statischen inneren Klasse nicht möglich, da sie als extra Klasse gezählt wird, die im gleichen Paket liegt. Der Zugriff von außen auf innere Klassen gelingt mit der Schreibweise ÄußereKlasse.InnereKlasse; der Punkt wird also so verwendet, wie wir es von den Paketen als Namensraum gewöhnt sind. Die innere Klasse muss einen anderen Namen als die äußere haben. Erlaubt sind die Modifizierer abstract, final und Sichtbarkeitsmodifizierer.

Umsetzung der inneren Typen

Die Sun-Entwickler haben es geschafft, die Einführung von inneren Klassen ohne Änderung der virtuellen Maschine über die Bühne zu bringen. Der Compiler generiert aus den inneren Typen nämlich einfach normale Klassendateien, die jedoch mit einigen Spezialfunktionen ausgestattet sind. Für die geschachtelten inneren Typen generiert der Compiler neue Namen nach dem Muster: ÄußererTyp$InnererTyp, das heißt: ein Dollar-Zeichen trennt die Namen von äußerem und innerem Typ.


Galileo Computing - Zum Seitenanfang

6.11.2 Mitglieds- oder Elementklassen Zur nächsten ÜberschriftZur vorigen Überschrift

Eine Mitgliedsklasse (engl. member class), auch Elementklasse genannt, ist ebenfalls vergleichbar mit einem Attribut, nur ist es nicht statisch. (Statische innere Klassen lassen sich aber auch als statische Mitgliedsklassen bezeichnen.) Deklarieren wir eine innere Mitgliedsklasse Room in House:

Listing 6.93 com.tutego.insel.inner.House.java, Ausschnitt

class House 
{ 
  private String owner = "Ich"; 
 
  class Room 
  { 
    void ok() 
    { 
      System.out.println( owner ); 
    } 
    // static void error() { } 
  } 
}

Ein Exemplar der Klasse Room hat Zugriff auf alle Eigenschaften von House, auch der privaten. Eine wichtige Eigenschaft ist, dass innere Mitgliedsklassen selbst keine statischen Eigenschaften deklarieren dürfen. Der Versuch in unserem Fall führt zum Compilerfehler:

The method error cannot be declared static; static methods can only be declared  
in a static or top level type

Exemplare innerer Klassen erzeugen

Um ein Exemplar von Room zu erzeugen, muss ein Exemplar der äußeren Klasse existieren. Das ist eine wichtige Unterscheidung gegenüber den statischen inneren Klassen von weiter oben. Statische innere Klassen existieren auch ohne Objekt der äußeren Klasse.

In einem Konstruktor oder in einer Objektmethode der äußeren Klassen kann einfach mit dem new-Operator ein Exemplar der inneren Klasse erzeugt werden. Kommen wir von außerhalb – oder von einem statischen Block der äußeren Klasse – und wollen Exemplare der inneren Klasse erzeugen, so müssen wir bei Elementklassen sicherstellen, dass es ein Exemplar der äußeren Klasse gibt. Die Sprache schreibt eine spezielle Form für die Erzeugung mit new vor, die folgendes allgemeine Format besitzt:

referenz.new InnereKlasse(...)

Dabei ist ref eine Referenz vom Typ der äußeren Klasse. Um in der main()-Funktion vom Haus ein Room-Objekt aufzubauen, schreiben wir:

Listing 6.94 com.tutego.insel.inner.House.java, main()

House h = new House(); 
Room  r = h.new Room();

oder auch in einer Zeile:

Room  r = new House().new Room();

Die this-Referenz

Möchte eine innere Klasse In auf die this-Referenz der umgebenden Klasse Out zugreifen, schreiben wir Out.this. Wenn sich Variablen überdecken, so schreiben wir Out.this.Eigenschaft, um an die Eigenschaften der äußeren Klasse zu gelangen.

Listing 6.95 com.tutego.insel.inner.HouseRoomChair.java, HouseRoomChair

class HouseRoomChair 
{ 
  String s = "House"; 
 
  class Room 
  { 
    String s = "Room"; 
 
    class Chair 
    { 
      String s = "Chair"; 
      void out() 
      { 
        System.out.println( s );                     // Chair 
        System.out.println( this.s );                // Chair 
        System.out.println( Chair.this.s );          // Chair 
        System.out.println( Room.this.s );           // Room 
        System.out.println( HouseRoomChair.this.s ); // House 
      } 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    new House().new Room().new Chair().out(); 
  } 
}

Hinweis Hinweis Elementklassen können beliebig geschachtelt sein, und da der Name eindeutig ist, gelangen wir mit Klassenname.this immer an die jeweilige Eigenschaft.


Betrachten wir das obere Beispiel, dann lassen sich Objekte für die inneren Klassen House, Room und Chair wie folgt erstellen:

House h            = new House();    // Exemplar von House 
House.Room r       = h.new Room();   // Exemplar von Room in h 
House.Room.Chair c = r.new Chair();  // Exemplar von Chair in r 
c.out();                             // Methode von Chair

Damit ist auch deutlich geworden, dass die Qualifizierung mit dem Punkt bei House.Room.Chair nicht automatisch bedeutet, dass House ein Paket mit dem Unterpaket Room ist, in dem die Klasse Chair existiert. Das macht es für die Lesbarkeit nicht gerade einfacher, und es droht eine Verwechslungsgefahr zwischen inneren Klassen und Paketen. Deshalb sollte die Namenskonvention befolgt werden: Klassennamen beginnen mit Großbuchstaben, Paketnamen mit Kleinbuchstaben.

Vom Compiler generierte Klassendateien

Für das Beispiel House und Room erzeugt der Compiler die Dateien House.class und House$Room.class. Damit die innere Klasse an die Attribute der äußeren gelangt, wird in jedem Exemplar der inneren Klasse eine Referenz auf das zugehörige Objekt der äußeren Klasse gelegt. Damit kann die innere Klasse auch auf nichtstatische Attribute der äußeren Klasse zugreifen. Für die innere Klasse ergibt sich folgendes Bild in House$Room.class:

class HouseBorder$Room 
{ 
  final House this$0; 
 
  House$Room( House house ) 
  { 
    this$0 = house; 
  } 
  // ... 
}

Die Variable this$0 referenziert das Exemplar House.this, also die zugehörige äußere Klasse. Die Konstruktoren der inneren Klasse erhalten einen zusätzlichen Parameter vom Typ House, um die this$0-Variable zu initialisieren.

Zugriffsrechte

Eine innere Klasse kann auf alle Attribute der äußeren Klasse zugreifen. Da innere Klassen als ganz normale Klassen übersetzt werden, stellt sich allerdings die Frage, wie sie das genau macht. Auf öffentliche Variablen kann jede andere Klasse ohne Tricks zugreifen, so auch die innere. Und da eine innere Klasse als normale Klassendatei im gleichen Paket sitzt, kann sie ebenfalls ohne Verrenkungen auf paketsichtbare und protected-Eigenschaften der äußeren Klasse zugreifen. Eine innere Klasse kann jedoch auch auf private Eigenschaften zurückgreifen, eine Designentscheidung, die sehr umstritten ist und lange kontrovers diskutiert wurde. Doch wie ist das zu schaffen, ohne gleich die Zugriffsrechte des Attributs zu ändern? Der Trick ist, dass der Compiler eine synthetische Funktion in der äußeren Klasse einführt:

class House 
{ 
  private String owner; 
 
  static String access$0( House house ) 
  { 
    return house.owner; 
  } 
}

Die statische Funktion access$0() ist der Helfershelfer, der für ein gegebenes House das private Attribut nach außen gibt. Da die innere Klasse einen Verweis auf die äußeren Klasse pflegt, gibt sie diesen beim gewünschten Zugriff mit, und die access$0()-Methode erledigt den Rest.

Pro von der inneren Klasse genutztes privates Attribut erzeugt der Compiler eine solche Funktion. Wenn wir eine weitere private Variable int size hinzunehmen, würde der Compiler ein int access$1(House) generieren.


Hinweis Hinweis Problematisch ist das bei Klassen, die in ein Paket eingeschmuggelt werden, denn die access$XXX()-Funktionen sind paketsichtbar. Es reicht dann ein Exemplar der äußeren Klasse, um über einen access$XXX()-Aufruf auf die privaten Variablen zuzugreifen, die eine innere Klasse nutzt. Glücklicherweise lässt sich gegen eingeschleuste Klassen in Java-Archiven leicht etwas unternehmen – sie müssen nur abgeschlossen werden, was bei Java sealing heißt.



Galileo Computing - Zum Seitenanfang

6.11.3 Lokale Klassen Zur nächsten ÜberschriftZur vorigen Überschrift

Lokale Klassen sind ebenfalls innere Klassen, die jedoch nicht einfach im Rumpf einer Klasse wie eine Eigenschaft, sondern direkt in Anweisungsblöcken von Methoden, Konstruktoren und Initialisierungsblöcken gesetzt werden. Lokale Schnittstellen sind nicht möglich. So soll im folgenden Beispiel die main()-Methode eine innere Klasse mit einem Konstruktor besitzen, der auf die finale Variable j zugreift.

Listing 6.96 com.tutego.insel.inner.FunInside.java, FunInside

public class FunInside 
{ 
  public static void main( String[] args ) 
  { 
    int i = 2; 
    final int j = 3; 
 
    class In 
    { 
      In() { 
        System.out.println( j ); 
//        System.out.println( i );    // Fehler, da i nicht final 
      } 
    } 
    new In(); 
  } 
}

Die Deklaration der inneren Klasse In ist wie eine Anweisung eingesetzt. Ein Sichtbarkeitsmodifizierer ist ungültig, und die Klasse darf keine Klassenmethoden und allgemeinen statischen Variablen deklarieren (finale Konstanten schon).

Jede lokale Klasse kann auf Methoden der äußeren Klasse zugreifen und zusätzlich auf die lokalen Variablen und Parameter, die mit dem Modifizierer final als unveränderlich ausgezeichnet sind. Liegt die innere Klasse in einer statischen Funktion, kann sie keine Objektmethode der äußeren Klasse aufrufen. Eine weitere Einschränkung im Vergleich zu den Elementklassen ist, dass die Modifizierer public, protected, private und static nicht erlaubt sind.


Galileo Computing - Zum Seitenanfang

6.11.4 Anonyme innere Klassen Zur nächsten ÜberschriftZur vorigen Überschrift

Anonyme Klassen gehen noch einen Schritt weiter als lokale Klassen. Sie haben keinen Namen und erzeugen immer automatisch ein Objekt; Klassendeklaration und Objekterzeugung sind zu einem Sprachkonstrukt verbunden. Die allgemeine Notation ist folgende:

new KlasseOderSchnittstelle() { /* Eigenschaften der inneren Klasse */ }

In dem Block geschweifter Klammern lassen sich nun Methoden und Attribute deklarieren oder Methoden überschreiben. Hinter new steht der Name einer Klasse oder Schnittstelle:

  • new Klassenname(Optionale Argumente) { ... }. Steht hinter new ein Klassentyp, dann ist die anonyme Klasse eine Unterklasse von Klassenname. Es lassen sich mögliche Argumente für den Konstruktor der Basisklasse angeben, den die anonyme innere Klasse automatisch mit super() aufruft.
  • new Schnittstellenname() { ... }. Steht hinter new der Name einer Schnittstelle, dann erbt die anonyme Klasse von Object und implementiert die Schnittstelle Schnittstellenname. Implementiert sie nicht die Operationen der Schnittstelle, ist das ein Fehler; wir hätten nichts davon, denn dann hätten wir eine abstrakte innere Klasse, von der sich kein Objekt erzeugen lässt.

Für anonyme innere Klassen gilt die Einschränkung, dass keine zusätzlichen extends- oder implements-Angaben möglich sind. Ebenso sind keine eigenen Konstruktoren möglich. Es gelten die gleichen Einschränkungen bezüglich der Methoden: es sind keine statischen Methoden und keine statischen nicht-finalen Variablen erlaubt.

Wir wollen eine innere Klasse schreiben, die Unterklasse von java.awt.Point ist. Sie soll die toString()-Methode überschreiben.

Listing 6.97 com.tutego.insel.inner.InnerToStringPoint.java, main()

Point p = new Point( 10, 12 ) { 
  @Override public String toString() { 
    return "(" + x + "," + y + ")"; 
  } 
}; 
 
System.out.println( p );    // (10,12)

Da sofort eine Unterklasse von Point aufgebaut wird, fehlt der Name der inneren Klasse. Das einzige Exemplar dieser anonymen Klasse lässt sich über die Variable p weiterverwenden.


Hinweis Hinweis Eine innere Klasse kann Methoden der Oberklasse überschreiben oder Operationen aus Schnittstellen implementieren. Neue Eigenschaften anzubieten, wäre zwar zulässig, aber nicht sinnvoll, es sei denn, die innere Klasse selbst nutzt die Methoden – dann müssen sie aber unsichtbar sein. Von außen sind Methoden, die nicht in der Oberklasse beziehungsweise Schnittstelle bekannt sind, nicht sichtbar. Deshalb sind auch anonyme Unterklassen von Object (ohne weitere implementierte Schnittstellen) nutzlos. (Wir lassen die Tatsache, dass eine Anwendung mit Reflection auf die Methoden zugreifen kann, außen vor.)


Umsetzung innerer anonymer Klassen

Auch für innere anonyme Klassen erzeugt der Compiler eine normale Klassendatei. Wir haben gesehen, dass im Fall einer »normalen« inneren Klasse die Notation ÄußereKlasse$InnereKlasse gewählt wird. Das klappt bei anonymen inneren Klassen natürlich nicht mehr, da uns der Name der inneren Klasse fehlt. Der Compiler wählt daher folgende Notation für Klassennamen: InnerToStringDate$1. Falls es mehr als eine innere Klasse gibt, folgen 2, 3 und so weiter.

Nutzung innerer Klassen für Threads

Sehen wir uns ein weiteres Beispiel für die Implementierung von Schnittstellen an. Um nebenläufige Programme zu implementieren, gibt es die Klasse Thread und die Schnittstelle Runnable. (Für das Beispiel greifen wir vor; Threads werden in Kapitel 10, »Threads und nebenläufige Programmierung«, genau beschrieben.)

Die Schnittstelle Runnable schreibt eine Operation run() vor, in der der parallel abzuarbeitende Programmcode gesetzt wird. Das geht gut mit einer inneren anonymen Klasse, die Runnable implementiert.

new Runnable() {     // Anonyme Klasse extends Object implements Runnable 
  public void run() { 
    ... 
  } 
}

Das Exemplar kommt in den Konstruktor der Klasse Thread. Der Thread wird mit start() angekurbelt. Damit folgt zusammengesetzt und mit Implementierung von run():

Listing 6.98 com.tutego.insel.inner.FirstThread, main()

new Thread( new Runnable() { 
  public void run() { 
    for ( int i = 0; i < 10; i++ ) 
      System.out.printf( "%d ", i ); 
  }; 
} ).start(); 
 
for ( int i = 0; i < 10; i++ ) 
  System.out.printf( "%d ", i );

In der Ausgabe wird zum Beispiel Folgendes erscheinen (hier komprimiert):

0 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9

Der neue Thread beginnt mit der 0 und wird dann unterbrochen. Der main-Thread kann in einem Zug 0 bis 9 ausgeben. Danach folgt wieder der erste Thread und kann den Rest ausgeben. Ausführliche Informationen zu Threads vermittelt Kapitel 10.

Eclipse
Keyboard Strg + Leertaste nach der geschweiften Klammer listet eine Reihe von Methoden auf, die wir uns von Eclipse implementieren lassen können. Da entscheiden wir uns doch für run().


Konstruktoren innerer anonymer Klassen

Da anonyme Klassen keinen Namen haben, muss für Konstruktoren ein anderer Weg gefunden werden. Hier helfen Exemplarinitialisierungsblöcke, das heißt, die Blöcke in geschweiften Klammern direkt innerhalb einer Klasse.

Dazu ein Beispiel: Die anonyme Klasse ist eine Unterklasse von Point und initialisiert im Konstruktor einen Punkt mit den Koordinaten –1, –1. Aus diesem speziellen Punkt-Objekt lesen wir dann die Koordinaten wieder aus.

Listing 6.99 com.tutego.insel.inner.AnonymousAndInside.java, main()

java.awt.Point p = new java.awt.Point() { { x = –1; y = –1; } }; 
 
System.out.println( p.getLocation() );  // java.awt.Point[x=-1,y=-1] 
 
System.out.println( new java.awt.Point( –1, 0 ) 
{ 
  { 
    y = –1; 
  } 
}.getLocation() );                      // java.awt.Point[x=-1,y=-1]

super()

Innerhalb eines »anonymen Konstruktors« kann kein super() verwendet werden, um den Konstruktor der Oberklasse aufzurufen. Dies liegt daran, dass automatisch ein super() in den Initialisierungsblock eingesetzt wird. Die Parameter für die gewünschte Variante des (überladenen) Oberklassen-Konstruktors werden am Anfang der Deklaration der anonymen Klasse angegeben. Dies zeigt das zweite Beispiel.

System.out.println( new Point(-1, 0) { { y = –1; } }.getLocation() );

Beispiel Beispiel Wir initialisieren ein Objekt BigDecimal, das beliebig große Ganzzahlen aufnehmen kann. Im Konstruktor der anonymen Unterklasse geben wir anschließend den Wert mit der geerbten toString()-Methode aus:

new java.math.BigDecimal( "12345678901234567890" ) { 
  { System.out.println( toString() ); } 
};


Galileo Computing - Zum Seitenanfang

6.11.5 this und Vererbung topZur vorigen Überschrift

Wenn wir ein qualifiziertes this verwenden, dann bezeichnet C.this die äußere Klasse, also das umschließende Exemplar. Gilt jedoch die Beziehung C1.C2. ... Ci. ... Cn., haben wir mit Ci.this ein Problem, wenn Ci eine Oberklasse von Cn ist. Es geht also um den Fall, dass eine textuell umgebende Klasse zugleich auch Oberklasse ist. Das eigentliche Problem besteht darin, dass hier zweidimensionale Namensräume hierarchisch kombiniert werden. Die eine Dimension sind die Bezeichner beziehungsweise Methoden aus den lexikalisch umgebenden Klassen, die andere Dimension die ererbten Eigenschaften aus der Oberklasse. Hier sind beliebige Überlappungen und Mehrdeutigkeiten denkbar. Durch diese ungenaue Beziehung zwischen inneren Klassen und Vererbung kam es unter JDK 1.1 und 1.2 zu unterschiedlichen Ergebnissen.

Im nächsten Beispiel soll von der Klasse Shoe die innere Klasse LeatherBoot den Shoe erweitern und die Methode out() überschreiben.

Listing 6.100 com.tutego.insel.inner.Shoe.java, Shoe

public class Shoe 
{ 
  void out() 
  { 
    System.out.println( "Ich bin der Schuh des Manitu." ); 
  } 
 
  class LeatherBoot extends Shoe 
  { 
    void what() 
    { 
      Shoe.this.out(); 
    } 
 
    @Override 
    void out() 
    { 
      System.out.println( "Ich bin ein Shoe.LeatherBoot." ); 
    } 
  } 
 
  public static void main( String[] args ) 
  { 
    new Shoe().new LeatherBoot().what(); 
  } 
}

Legen wir in der main()-Funktion ein Objekt der Klasse LeatherBoot an, dann landen wir bei what() in der Klasse LeatherBoot, was Shoe.this.out() ausführt. Interessant ist aber, dass hier kein dynamisch gebundener Aufruf an out() vom LeatherBoot-Objekt erfolgt, sondern die Ausgabe von Shoe ist:

Ich bin der Schuh des Manitu.

Die überschriebene Ausgabe von LeatherBoot liefert die ähnliche aussehende Anweisung ((Shoe)this).out(). Vor 1.2 kam als Ergebnis immer diese Zeichenkette heraus.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.






<< zurück



Copyright © Galileo Press 2008
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de