Rheinwerk Computing < openbook > Rheinwerk 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 Eigene Klassen schreiben
6 Exceptions
7 Generics<T>
8 Äußere.innere Klassen
9 Besondere Klassen der Java SE
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Bits und Bytes und Mathematisches
13 Datenstrukturen und Algorithmen
14 Threads und nebenläufige Programmierung
15 Raum und Zeit
16 Dateien, Verzeichnisse und Dateizugriffe
17 Datenströme
18 Die eXtensible Markup Language (XML)
19 Grafische Oberflächen mit Swing
20 Grafikprogrammierung
21 Netzwerkprogrammierung
22 Verteilte Programmierung mit RMI
23 JavaServer Pages und Servlets
24 Datenbankmanagement mit JDBC
25 Reflection und Annotationen
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
geb., mit DVD
1482 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1506-0
Pfeil 8 Äußere.innere Klassen
  Pfeil 8.1 Geschachtelte (innere) Klassen, Schnittstellen, Aufzählungen
    Pfeil 8.1.1 Statische innere Klassen und Schnittstellen
    Pfeil 8.1.2 Mitglieds- oder Elementklassen
    Pfeil 8.1.3 Lokale Klassen
    Pfeil 8.1.4 Anonyme innere Klassen
    Pfeil 8.1.5 Zugriff auf lokale Variablen aus lokalen inneren und anonymen Klassen *
    Pfeil 8.1.6 »this« und Vererbung *

»Der Nutzen ist ein Teil der Schönheit.« – Albrecht Dürer (1471–1528)

8 Äußere.innere Klassen


Rheinwerk Computing - Zum Seitenanfang

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

Bisher haben wir Klassen, Schnittstellen und Aufzählungen kennengelernt, die entweder allein in der Datei oder zusammen mit anderen Typen in einer Datei, also einer Compilationseinheit deklariert wurden. Es gibt darüber hinaus die Möglichkeit, eine Klasse, Aufzählung oder Schnittstelle in andere Typdeklarationen hineinzunehmen. Das ist sinnvoll, denn die Motivation dahinter ist, noch mehr Details zu verstecken, denn es gibt sehr lokale Typdeklarationen, die keine größere Sichtbarkeit brauchen.

Für eine Klasse In, die in eine Klasse Out gesetzt wird, sieht das im Quellcode 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 Sprachregelung »innere Klassen«. (Aufzählungen werden vom Compiler in Klassen übersetzt und müssen daher nicht unbedingt gesondert behandelt werden. Natürlich lassen sich auch Aufzählungen innerhalb von Klassen oder Schnittstellen deklarieren.)

Die Java-Spezifikation beschreibt vier Typen von inneren Klassen, die im Folgenden vorgestellt 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.


Tabelle 8.1  Die vier Typen von inneren Klassen

Typ Beispiel

statische innere Klasse

class Out static class In {} }

Mitgliedsklasse

class Out class In }

lokale Klasse

class Out Out() class In }

anonyme innere Klasse

class Out Out() new Runnable() public void run() }; }



Hinweis Das Gegenteil von geschachtelten Klassen, also das, womit wir uns bisher die ganze Zeit beschäftigt haben, heißt »Top-Level-Klasse«. Die Laufzeitumgebung kennt nur Top-Level-Klassen, und geschachtelte innere Klassen werden letztendlich zu ganz »normalen« Klassendeklarationen.



Rheinwerk Computing - Zum Seitenanfang

8.1.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 8.1  com/tutego/insel/inner/Lamp.java, Lamp

public class Lamp
{
  static String s = "Huhu";
  int i = 1;

  static class Bulb
  {
    void output()
    {
      System.out.println( s );
//    System.out.println( i );   // Fehler Compilerfehler: i is not static
    }
  }

  public static void main( String[] args )
  {
    Bulb bulb = new Lamp.Bulb();  // oder Lamp.Bulb bulb = ...
    bulb.output();
  }
}

Die statische innere Klasse Bulb besitzt Zugriff auf alle anderen statischen Eigenschaften der äußeren Klasse Lamp, in unserem Fall auf die Variable s. Ein Zugriff auf Objektvariablen ist aus der statischen inneren Klasse heraus nicht möglich, da sie als gesonderte 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 vom Zugriff auf statische Eigenschaften her kennen und auch von den Paketen als Namensraum gewöhnt sind. Die innere Klasse muss einen anderen Namen als die äußere haben.

Modifizierer und Sichtbarkeit

Erlaubt sind die Modifizierer abstract, final und einige Sichtbarkeitsmodifizierer. Normale Top-Level-Klassen können paketsichtbar oder public sein; innere Klassen dürfen ebenfalls public oder paketsichtbar alternativ aber auch protected oder private sein. Eine private statische innere Klasse ist dabei wie eine normale private statische Variable zu verstehen: Sie kann nur von der umschließenden äußeren Klasse gesehen werden, aber nicht von anderen Top-Level-Klassen. protected an statischen inneren Typen ermöglicht für den Compiler einen etwas effizienteren Bytecode, ist aber ansonsten nicht in Gebrauch.

Umsetzung der inneren Typen *

Die Sun-Entwickler haben es geschafft, die Einführung von inneren Klassen in Java 1.1 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 so genannten synthetischen Methoden ausgestattet sind. Für die 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. Genauso heißt die entsprechende .class-Datei auf der Festplatte.


Rheinwerk Computing - Zum Seitenanfang

8.1.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 sie nicht statisch (statische innere Klassen lassen sich aber auch als statische Mitgliedsklassen bezeichnen). Deklarieren wir eine innere Mitgliedsklasse Room in House:

Listing 8.2  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 auf die privaten. Eine wichtige Eigenschaft ist, dass innere Mitgliedsklassen selbst keine statischen Eigenschaften deklarieren dürfen. Der Versuch führt in unserem Fall zu einem 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 Abschnitt 8.1.1 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. Java schreibt eine spezielle Form für die Erzeugung mit new vor, die folgendes allgemeine Format besitzt:

referenz.new InnereKlasse(...)

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

Listing 8.3  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 sie umgebenden Klasse Out zugreifen, schreiben wir Out.this. Wenn Variablen der inneren Klasse die Variablen der äußeren Klasse überdecken, so schreiben wir Out.this.Eigenschaft, um an die Eigenschaften der äußeren Klasse Out zu gelangen:

Listing 8.4  com/tutego/insel/inner/FurnishedHouse.java,FurnishedHouse

class FurnishedHouse
{
  String s = "House";

  class Room
  {
    String s = "Room";

    class Chair
    {
      String s = "Chair";

      void output()
      {
        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( FurnishedHouse.this.s );  // House
      }
    }
  }

  public static void main( String[] args )
  {
    new FurnishedHouse().new Room().new Chair().output();
  }
}

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 obige Beispiel, dann lassen sich Objekte für die inneren Klassen Room und Chair wie folgt erstellen:

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

Das Beispiel macht deutlich, dass die Qualifizierung mit dem Punkt bei FurnishedHouse.Room.Chair nicht automatisch bedeutet, dass FurnishedHouse ein Paket mit dem Unterpaket Room ist, in dem die Klasse Chair existiert. Die Doppelbelegung des Punkts verbessert die Lesbarkeit nicht gerade, und es droht Verwechslungsgefahr zwischen inneren Klassen und Paketen. Deshalb sollte die Namenskonvention beachtet 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, generiert der Compiler automatisch in jedes Exemplar der inneren Klasse eine Referenz auf das zugehörige Objekt der äußeren Klasse. Damit kann die innere Klasse auch auf nicht-statische 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. Da wir die Konstruktoren sowieso nicht zu Gesicht bekommen, kann uns das egal sein…

Erlaubte Modifizierer bei äußeren und inneren Klassen

Ist in einer Datei nur eine Klasse deklariert, kann diese nicht privat sein. Private innere Klassen sind aber legal. Statische Hauptklassen gibt es zum Beispiel auch nicht, aber innere statische Klassen sind legitim. Die folgende Tabelle fasst die erlaubten Modifizier noch einmal kompakt zusammen:


Tabelle 8.2  Erlaubte Modifizierer

Modifizierer erlaubt auf äußeren Klassen inneren Klassen äußeren Schnittstellen inneren Schnittstellen

public

ja

ja

ja

ja

protected

nein

ja

nein

ja

private

nein

ja

nein

ja

static

nein

ja

nein

ja

final

ja

ja

nein

nein

abstract

ja

ja

ja

ja


Zugriffsrechte *

Eine innere Klasse kann auf alle Attribute der äußeren Klasse zugreifen. Da eine innere Klasse als ganz normale Klasse übersetzt wird, 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 statische Methode in der äußeren Klasse einführt:

class House
{
  private String owner;

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

Die statische Methode 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ßere Klasse pflegt, gibt sie diesen beim gewünschten Zugriff mit, und die access$0()-Methode erledigt den Rest.

Für jedes von der inneren Klasse genutzte private Attribut erzeugt der Compiler eine solche Methode. Wenn wir eine weitere private Variable int size hinzunehmen, würde der Compiler ein int access$1(House) generieren.


Hinweis Problematisch ist das bei Klassen, die in ein Paket eingeschmuggelt werden. Nehmen wir an, House liegt im Paket p1.p2. Dann kann ein Angreifer seine Klassen auch in ein Paket legen, was p1.p2 heißt. Da die access$XXX()-Methoden paketsichtbar sind, können eingeschmuggelte Klassen die packetsichtbaren access$XXX()-Methoden aufrufen. Es reicht 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.



Rheinwerk Computing - Zum Seitenanfang

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

Lokale Klassen sind ebenfalls innere Klassen, die jedoch nicht einfach wie eine Eigenschaft im Rumpf einer Klasse, sondern direkt in Anweisungsblöcken von Methoden, Konstruktoren und Initialisierungsblöcken gesetzt werden. Lokale Schnittstellen sind nicht möglich.

Im folgenden Beispiel deklariert die main()-Methode eine innere Klasse mit einem Konstruktor, der auf die finale Variable j zugreift:

Listing 8.5  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 );    // Compiler error because i is not final
      }
    }
    new In();
  }
}

Die Deklaration der inneren Klasse In wird hier wie eine Anweisung eingesetzt. Ein Sichtbarkeitsmodifizierer ist bei inneren lokalen Klassen 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 Methode, kann sie keine Objektmethoden der äußeren Klasse aufrufen.


Rheinwerk Computing - Zum Seitenanfang

8.1.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 (das ist zum Beispiel dann nötig, wenn die Oberklasse keinen Standardkonstruktor deklariert).
  • 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 und nur Objektmethoden und finale statische Variablen erlaubt.

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

Listing 8.6  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 Eine innere Klasse kann Methoden der Oberklasse überschreiben, Operationen aus Schnittstellen implementieren und sogar neue Eigenschaften anbieten:

String s = new Object()
  String quote( String s ) { return String.format( "'%s'", s );
}.quote( "Juvy" );
System.out.println( s );  // 'Juvy'

Der neu deklarierte anonyme Typ hat eine Methode quote(), die direkt aufgerufen werden kann. Ohne diesen direkten Aufruf ist die quote()-Methode aber unsichtbar, denn der Typ ist ja anonym, und so sind nur die Methoden der Oberklasse (bei uns Object) beziehungsweise Schnittstelle bekannt. (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 14, »Threads und nebenläufige Programmierung«, genau beschrieben).

Die Schnittstelle Runnable schreibt eine Operation run() vor, in die 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 so erzeugte Exemplar kommt in den Konstruktor der Klasse Thread. Der Thread wird mit start() angekurbelt. Damit folgt zusammengesetzt und mit Implementierung von run():

Listing 8.7  com/tutego/insel/inner/FirstThread, main()

new Thread( new Runnable() {
  @Override 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 14.

Eclipse
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 *

Der Compiler setzt anonyme Klassen in normale Klassendateien um. Jede Klasse kann einen eigenen Konstruktor deklarieren, und auch für anonyme Klassen sollte das möglich sein, um Initialisierungscode dort hineinzusetzen. Da aber anonyme Klassen keinen Namen haben, muss für Konstruktoren ein anderer Weg gefunden werden. Hier helfen Exemplarinitialisierungsblöcke, also Blöcke in geschweiften Klammern direkt innerhalb einer Klasse, die wir schon in Kapitel 5, »Eigene Klassen schreiben«, vorgestellt haben. Exemplarinitialisierer gibt es ja eigentlich gar nicht im Bytecode, sondern der Compiler setzt den Programmcode automatisch in jeden Konstruktor. Obwohl anonyme Klassen keinen direkten Konstruktor haben können, gelangt doch über den Exemplarinitialisierer Programmcode in den Konstruktor der Bytecode-Datei.

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 8.8  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]

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


Rheinwerk Computing - Zum Seitenanfang

8.1.5 Zugriff auf lokale Variablen aus lokalen inneren und anonymen Klassen *  Zur nächsten ÜberschriftZur vorigen Überschrift

Lokale und innere Klassen können auf die lokalen Variablen beziehungsweise Parameter der umschließenden Methode lesend zugreifen, jedoch nur dann, wenn die Variable final ist. Verändern können lokale und innere Klassen diese Variablen natürlich nicht, denn final verbietet einen zweiten Schreibzugriff.

Ist eine Veränderung nötig, ist ein Trick möglich. Zwei Lösungen bieten sich an:

  • die Nutzung eines finalen Feldes der Länge 1, welches das Ergebnis aufnehmen kann
  • die Nutzung von AtomicXXX-Klassen aus dem java.util.concurrent.atomic-Paket, die ein primitives Element oder eine Referenz aufnehmen

Ein Beispiel:

Listing 8.9  com/tutego/insel/inner/ModifyLocalVariable.java, main()

public static void main( String[] args )
{
  final int[] result1 = { 0 };
  final String[] result2 = { null };
  final AtomicInteger result3 = new AtomicInteger();
  final AtomicReference<String> result4 = new AtomicReference<String>();

  System.out.println( result1[0] );     // 0
  System.out.println( result2[0] );     // null
  System.out.println( result3.get() );  // 0
  System.out.println( result4.get() );  // null

  new Object(){{
      result1[0] = 1;
      result2[0] = "Der Herr der Felder";
      result3.set( 1 );
      result4.set( "Wurstwasser-Wette" );
  }};

  System.out.println( result1[0] );     // 1
  System.out.println( result2[0] );     // Der Herr der Felder
  System.out.println( result3.get() );  // 1
  System.out.println( result4.get() );  // Wurstwasser-Wette
}

Die AtomicXXX-Klassen haben eigentlich die Aufgabe, Schreib- und Veränderungsoperationen atomar durchzuführen, können jedoch in diesem Szenario hilfreich sein.


Rheinwerk Computing - Zum Seitenanfang

8.1.6 »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. Das haben wir schon im Abschnitt »Die »this«-Referenz« in Abschnitt 8.1.2 kennengelernt. 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 müssen. 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 8.10  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 statischen main()-Methode 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 ähnlich aussehende Anweisung ((Shoe)this).out(). Vor Version 1.2 kam als Ergebnis immer diese Zeichenkette heraus, aber das ist Geschichte und nur eine historische Randnotiz.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
 <<   zurück
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Java SE Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2011
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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de