Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java 2 von Friedrich Esser
Designmuster und Zertifizierungswissen
Zum Katalog
gp Kapitel 8 Innere Klassen
  gp 8.1 Arten von inneren Klassen
  gp 8.2 Statische innere Klassen und Interfaces
  gp 8.3 Nicht statische innere Klassen
  gp 8.4 Member-Klassen
  gp 8.5 Lokale Klassen
  gp 8.6 Anonyme Klassen
  gp 8.7 Firewall-Idiom: Quasi-Objekte von Interfaces
  gp 8.8 Generisches Verhalten
  gp 8.9 Zusammenfassung
  gp 8.10 Testfragen

Kapitel 8 Innere Klassen

Innere Klassen – ihrem Namen entsprechend in anderen Klasse enthalten – lösen gewisse Probleme einfach und elegant. Abhängig von der Intention bzw. Verwendung können innere Klassen auf vier verschiedene Arten deklariert werden.
Neben Klassen gibt es auch innere Interfaces, die allerdings nur als statischer Typ sinvoll sind.
Bedingt durch die Flexibilität, die sich aufgrund der Kombinationsmöglichkeiten von inneren Klassen und Modifikatoren ergeben, sind die Syntax-Regeln zu inneren Klassen nicht unbedingt trivial.

Innere Klassen (inner classes) wurden erst nachträglich in Java ab JDK 1.1 eingeführt. Überraschenderweise waren nur kleine Syntaxerweiterungen, aber keine Änderung an der JVM notwendig.

Modifikatoren zu inneren Klassen

Bedingt durch ihre Deklaration innerhalb einer anderen Klasse, sind neben den Zugriffs-Modifikatoren noch die Modifikatoren abstract, final oder static erlaubt.

Top-Level- vs. innere Klasse

Neben den rein formalen bzw. syntaktischen Unterschieden gegenüber den regulären Top-Level-Klassen, die direkt auf Package-Ebene deklariert sind, werden abschließend Einsatzmöglichkeiten von inneren Klassen besprochen.

Interfaces sind immer statisch

Da innere Interfaces ausschließlich in statischer Form auftreten, werden sie nur kurz im folgenden Abschnitt behandelt.


Galileo Computing

8.1 Arten von inneren Klassen  downtop

Zur Deklaration von inneren Klassen bzw. Interfaces wird – im Gegensatz zur Verwendung – keine neue Syntax benötigt.

Dadurch, dass sie zu einer äußeren Klasse gehören, unterliegen sie allerdings denselben Zugriffsrestriktionen wie alle anderen Member der äußeren Klasse.

Innere Klassen lassen sich in zwei grundlegend verschiedene Typen einteilen: statische und nicht statische (siehe Abb. 8.1).

Typisierung von inneren Klassen


Abbildung
Abbildung 8.1   Übersicht über innere Klassen bzw. Interfaces

Nested-Top-Level-Klasse

Die Variante »statische innere Klassen« wird häufig auch als nested top-level class bezeichnet.

Ein grundlegender semantischer Unterschied besteht nur zwischen den statischen und den nicht statischen Typen.

Lokale bzw. anonyme Klassen sind entweder vom statischen oder aber nicht statischen Typ (siehe Abb. 8.1).

Obwohl selten genutzt, gilt:

Schachtelung von inneren Klassen

gp  Klassen können beliebig tief ineinander geschachtelt werden, wobei aber nicht jede Kombination der vier Typen erlaubt ist.

Icon

Neben der normalen Regel, dass Felder innerhalb einer Klasse unterschiedliche Namen haben müssen, gilt für alle inneren Klassen bzw. Interfaces noch folgende zusätzliche Namens-Regel:

Namens-Regel für innere Klassen

gp  Der Name einer inneren Klasse bzw. eines Interfaces darf nicht identisch mit dem einer äußeren Klasse sein (d.h., mit dem Namen der Klasse, die sie bzw. es enthält).

Nachfolgend werden zuerst Syntax und Semantik der verschiedenen Typen besprochen.


Galileo Computing

8.2 Statische innere Klassen und Interfaces  downtop

Äußere Klasse:
Sub-Package für statische innere Klassen

Für statische innere Klassen und Interfaces bildet eine Top-Level-Klasse eine Art Sub-Package, d.h. einen weiteren Namens-Raum unterhalb des Packages.


Abbildung
Abbildung 8.2   Top-Level-Klasse als Sub-Package

Sematische Einheit: äußere Klassen zu statischen inneren

Nested-Top-Level-Klassen machen dann Sinn, wenn diese nur innerhalb der äußeren Klasse oder nur in Verbindung mit der äußeren Klasse eingesetzt werden sollen. Obwohl möglich, ist deshalb der Import einer Nested-Top-Level-Klasse ohne ihre äußere Klasse nicht gerade sinnvoll.

Stehen die statischen Klassen bzw. Interfaces von außen im Zugriff, verhalten sie sich wie andere Top-Level-Klassen bzw. Interfaces. Allerdings haben sie aufgrund ihrer Member-Eigenschaft und des Namens-Raums eine besonders enge Bindung an ihre äußere Klasse.

Deklaration von statischen inneren Klassen/Interfaces

Die Deklaration erfolgt wie bei normalen statischen Membern:

class OuterClass {
  //...
  [icModifiers] static 
class StaticInnerClass 

[extends SuperClass] [implements Interface1, ...]
{...}
  [icModifiers] [static] interface 
InnerInterface 

[
extends Interface1,...]
{...}
//...
}

Als icModifiers sind abstract, final sowie die Zugriffs-Modifikatoren erlaubt.

Innerhalb der äußeren Klasse ist Anlage und Zugriff auf innere Klassen wie auf andere Top-Level-Klassen möglich.

erweiterte Syntax für Zugriffe auf nested top-level Klassen

Die Anlage und der Zugriff von außen auf Instanzen von nicht private erklärten statischen inneren Klassen, erfolgt mit einer erweiterten Punkt-Notation:

     sic= new OuterClass.StaticInnerClass(); 
// Anlage
     OuterClass.StaticInnerClass[.member]    // Zugriff
gp  Die Anlage einer Instanz einer statischen inneren Klasse ist völlig unabhängig von den Instanzen der äußeren Klasse.

Insbesondere gibt es also keine besonderen wechselseitigen Zugriffsmöglichkeiten auf Instanz-Variablen.

Icon

Für Nested-Top-Level-Klassen bzw. Interfaces gelten folgende zusätzliche Regeln:

Beziehungen der statischen inneren Klasse zur äußeren

1. Statische innere Klassen und Interfaces können ineinander geschachtelt werden.
2. Alle statischen inneren Klassen sowie die äußere Klasse können untereinander auf alle statischen Member zugreifen.
3. Statische Klassen können Subklassen von äußeren sein.2
4. Statische und äußere Klassen können ein inneres Interface implementieren.

Beispiele

Inneres Interface und statische Klasse

class Outer implements 
InnerI {                // 4. Regel
  private static int si= 0;
  private static char c= '1';
  // InnerI implizit static, nur innerhalb von Outer 
im Zugriff
  private interface InnerI { void f1(); 
}  
  static class InnerC 
implements InnerI {      // 4. Regel
    private static int si= Outer.si+2;         // 2. Regel
    public static float f= 1.23f;
    public void f1() { System.out.println(si+" "+c); }
  }
  public void f1() {
    System.out.println(InnerC.si);             // 2. Regel
  }
}

Von außen können Objekte innerer Klassen mit der oben vorgestellten Syntax manipuliert werden:

Outer o= new Outer();        // erzeugt kein InnerC-Objekt!

Instanz-Anlage einer statischen inneren Klasse

// Anlage einer Instanz einer Nested-Top-Level-Klasse InnerC
Outer.InnerC oi= new Outer.InnerC(); 
   
System.out.println(Outer.InnerC.f); 
       // :: 1.23
o.f1();                                   // :: 2
oi.f1();                                  // :: 2 1

Schachtelung von inneren statischen Klassen/Interfaces

Im nächsten Beispiel werden die Modifikatoren abstract und final mit einbezogen:

// Vermeidung von Namenskollisionen durch 
Punkt-Notation
abstract class Outer2 implements InnerI, InnerC1.InnerI { interface InnerI { void f1(); }
  // Vermeidung in umgekehrter Richtung
abstract static class InnerC1 implements Outer.InnerI, InnerI {

Namensregel gilt für geschachtelte innere Klassen

    // weiteres Interface mit gleichem 
Namen wie oben
static interface InnerI { void f2(); }

// weitere innere Klasse mit unterschiedlichem Namen
static class InnerC2 {}
}

  // nicht abstract, da Implementation von 
f1() und f2()
static final class InnerC3 extends Outer { void f1() {}; void f2() {}; } }

Die zwei Interfaces stehen zwar auf verschiedenen Stufen, sind aber nicht ineinander geschachtelt und können somit den gleichen Namen haben. Zweideutigkeiten müssen mit Hilfe der Punkt-Notation beseitigt werden.

Fehlerhafte Vererbungsrichtung

Umgekehrt kann nach der dritten Regel keine äußere Klasse Outer Subklasse einer statischen inneren Klasse Inner sein:

// C-Fehler: zyklische Vererbung 
class Outer extends Inner { 
  static class Inner { ... }
}

Galileo Computing

8.2.1 Design-Beispiel: 2D-Klassen  downtop

2D-Klassen:
äußere abstrakte Klasse mit inneren statischen
Subklassen

Das Konstrukt »Erweiterung einer abstrakten äußeren Klasse durch eine statische innere Klasse« wird u.a. im neuen Package java.awt.geom für 2D-Klassen eingesetzt.

Hier ein Deklarations-Schnipsel:

public abstract class Point2D implements 
Cloneable {
  public static class Float extends Point2D { 
    //...
  }
  public static class Double extends 
Point2D {
    //...
  }
}

Die konkreten Punkte haben dann entweder float- oder double-Koordinaten, können aber unabhängig davon alle als Typ Point2D angesprochen werden.

Template-Prinzip für 2D-Objekte

Nach dem Template-Prinzip »Programmiere gegen ein Interface« (siehe 6.6) werden – wenn möglich – die Instanzen der konkreten statischen inneren 2D-Klassen über Referenzen von Interfaces wie z.B. Shape angesprochen:

   Shape circle= new Ellipse2D.Double(10.0,10.0,10.0,10.0);

Das Interface Shape steht dabei für ein generisches 2D-Objekt, welches offen oder geschlossen sein kann, wobei natürlich die fundamentalen Shape-Operationen wie z.B. »Ausfüllen« oder »Transformieren« von allen Shape-Objekten implementiert sind.


Galileo Computing

8.3 Nicht statische innere Klassen  downtop

Alle nicht statischen inneren Klassen haben unabhängig von ihrer Ausprägung als Member-, lokale oder anonyme Klasse Gemeinsamkeiten in der Instanz-Bindung und den Regeln.

Ihre Instanzen verhalten sich entweder wie Felder von Instanzen der äußeren Klasse oder wie lokale Variablen in Blöcken und Methoden. Benötigt man nur eine lokale Variable, reicht eine lokale Klasse ohne Namen.

Von allen drei Subtypen bilden zweifellos die Member-Klassen in Bezug auf Syntax und Semantik die schwierigste Untergruppe.


Galileo Computing

8.4 Member-Klassen  downtop

Icon

Instanzen von Member-Klassen haben Ähnlichkeit mit Feldern, sie haben allerdings besondere Regeln:

Beziehungen der Member-Klasse zur äußeren Klasse

1. Eine Instanz einer Member-Klasse ist mit genau einer vorher erschaffenen Instanz der äußeren Klasse verbunden (siehe Abb. 8.3).
2. Member-Klassen können bis auf Konstanten (static final erklärte Felder) keine statischen Felder oder Methoden besitzen.
3. Member-Klassen haben Zugriff auf alle Felder und Methoden der äußeren Klasse, inklusive der private deklarierten.
4. Member-Klassen können ineinander geschachtelt werden.
5. Member-Klassen können Subklassen von äußeren Klassen sein.

Abbildung
Abbildung 8.3   Assoziation einer Member-Klasse zur äußeren Klasse

Member-Klasse vs. Feld

gp  In einer äußeren Instanz können beliebig viele Instanzen einer Member-Klasse »enthalten« sein (siehe Abb. 8.3).

Icon

Dies unterscheidet eine Member-Klasse von einer normalen Feldreferenz, die auf höchstens ein Objekt verweist.

Member-Klassen:
Schachtelung und Subtyp-Beziehung

Die vierte und fünfte Regel sollte nicht benötigt werden. Solche Klassen-Konstrukte führen nur zu schwer durchschaubaren Inklusions-Beziehungen (siehe auch erster Fall in 8.4.2).

Der Zugriff auf den Typ oder eine Konstante einer Member-Klasse erfolgt wie bei Feldern mit Hilfe der erweiterten Punkt-Notation.

Beispiel

Es wird zuerst der Unterschied zwischen Member-Klassen und statischen inneren Klassen verdeutlicht.

Vergleich:
statische innere Klasse und Member-Klasse

class Outer {
  static class StaticC {}
  class MemberC { 
    MemberC() { System.out.println("MemberC");} 
  }
  // zur statischen Methode gibt es keine 
Outer-Instanz
static void test() { StaticC sc= new StaticC(); //ok, da statisch // MemberC mc= new MemberC(); C-Fehler ¨ } // innerhalb einer Instanz-Methode Anlage möglich
void f() { new MemberC(); } ¦ }
public class Test {
  public static void main(String[] args) {
    Outer.StaticC sc= new Outer.StaticC();  // ok, 
da statisch
    // Outer.MemberC mc= new Outer.MemberC(); C-Fehler 
         Æ
    new Outer().f();                        // :: 
MemberC     Ø
  }                                        
}

zu ¨ und Æ: C-Fehler aufgrund der ersten Regel, denn es existiert keine Instanz der äußeren Klasse.

zu ¦ und Ø: In Instanz-Methoden erzeugte Member-Objekte werden automatisch mit der zugehörigen Instanz der äußeren Klasse verbunden.


Galileo Computing

8.4.1 Anlage und Zugriff auf Instanzen einer Member-Klasse  downtop

Für eine flexible Anlage von Objekten einer Member-Klasse und Eindeutigkeit bei Zugriffen ist wieder eine Syntaxerweiterung notwendig:

Syntax zur Anlage einer Member-Klassen-Instanz

gp  Die Anlage einer Instanz einer inneren Klasse, verbunden mit einer Instanz der (direkt übergeordneten) äußeren Klasse, erfolgt durch

outerClassInstance.new MemberClassConstructor()

Ist die Instanz der äußeren Klasse this, so kann – wie im letzten Beispiel bei der Methode f() zu sehen – anstatt this.new nur new geschrieben werden.

Syntax für Zugriff auf Member der äußeren Klasse

gp  Der Zugriff auf Felder bzw. Methoden einer äußeren Klasse erfolgt bei Zweideutigkeit aus einer Member-Klasse mit:

OuterClass.this.field bzw. OuterClass.this.method()

Eindeutigkeit bei geschachtelten Member-Klassen

Auch bei geschachtelten inneren Klassen ist der Zugriff von Member-Klassen auf Felder bzw. Methoden aller äußeren Klassen aufgrund der Namens-Regel in 8.1 immer eindeutig.

Beispiel

Ein Zugriff von einem Member auf die Instanz der äußeren Klasse ist immer möglich. Die Umkehrung gilt nicht.

class Outer {  
  private int i= 1;
  // Zugriff auf Member-Objekte nur durch explizite Referenz
MemberC m;
  class MemberC {
    public static final int CONST= 10;        // 2. Regel 8.4
    private int i;
    // dieser Konstruktor setzt explizit eine Member-Referenz
MemberC() { m= this; }

Zugriff auf Felder der äußeren Klasse

  // dieser Konstruktor setzt keine Referenz 
auf Member
// impliziter Zugriff vom Member auf äußeres Objekt
MemberC(int i) { this.i= Outer.this.i + neg() + i; // 3. Regel 8.4 }
    int geti() { return i; }
  }
  private int neg() { return -i; }
  void showMember() { System.out.println(m); }
}

Im Testcode werden zwei Member-Instanzen angelegt:

Outer o= new Outer();
Outer.MemberC mo= o.new MemberC(1);      // 
erweiterte Syntax
System.out.println(Outer.MemberC.CONST); // :: 10
o.showMember();                          // :: null
o.new MemberC();
o.showMember();                // :: p08.Outer$MemberC@7007db1
System.out.println(o.m.geti());// :: 0
System.out.println(mo.geti()); // :: 1

Erklärung: Die erste Member-Instanz mo hat zwar Zugriff auf das äußere Objekt o, aber nicht umgekehrt. Erst die zweite Member-Instanz wird mit Hilfe des Konstruktors explizit im Objekt o durch m referenziert.

this$0:
unsichtbare Referenz auf die äußere Klasse

Hinter dem impliziten Registrier-Mechanismus des externen Objekts steht eine unsichtbare Referenz this$0 im Member-Objekt (siehe Abb. 8.4), die bei der Anlage des Member-Objekts gesetzt wird.


Abbildung
Abbildung 8.4   Member-Bindung an eine Instanz der äußeren Klasse

Eine Member-Registrierung ist dagegen implizit schlecht möglich, da die Anzahl der Member-Objekte beliebig groß ist.

Tiefe Schachtelung von
Member-Klassen Icon

Beispiel

Zur Abschreckung wird die vierte Regel aus 8.4 anhand der drei ineinander geschachtelten Klassen Outer, Member1 und Member2 demonstriert. In Member2 werden die gleich lautenden int-Felder i der drei Klassen summiert:

class Outer {
  private int i;
  public class Member1 {
    private int i;
    public class Member2 {                 // 4. Regel 
8.4
      private int i;
      Member2(int i1, int i2, int i3) { 
        Outer.this.i= i1; Member1.this.i= i2; i= i3;
      }
      int sum() { return Outer.this.i + Member1.this.i + i;}
    }
  }
  // f() demonstriert verschiedene Anlagen 
von Member-Instanzen
void f() { Outer.Member1 m1= new Member1(); Outer.Member1.Member2 m2= m1.new Member2(10,20,30); System.out.println( this.new Member1().new Member2(1,2,3).sum()); // :: 6 ¨ System.out.println(m2.sum()); // :: 51 } }

Die Ausgabe in der Methode f() wird z.B. durch folgenden Testcode erzeugt:

   new Outer().f();

Erklärung: Das zweite Ergebnis 51 kommt daher, dass es nur eine Outer-Instanz gibt, die durch die Zeile ¨ auf 1 gesetzt wird (siehe Abb. 8.5).


Abbildung
Abbildung 8.5   Logischer Zusammenhang der Instanzen in Methode f()

Fazit: Vermeide Schachtelungen von Member-Klassen!


Galileo Computing

8.4.2 Member-Klassen und Vererbung  downtop

Wie bereits bei statischen inneren Klassen gezeigt, kann eine äußere Klasse nicht von einer Member-Klasse abgeleitet werden:

Fehlerhafte Vererbungsrichtung

// C-Fehler: zyklische Vererbung
class Outer extends MemberC { class MemberC { } }

Dagegen sind drei weitere Fälle nicht ausgeschlossen, von denen allerdings nur der dritte Fall wichtig ist. Die ersten beiden werden nur kurz angeführt, aber nicht näher besprochen, da eine sinnvolle Anwendung fehlt.

Icon

1. Eine Member-Klasse ist Subklasse der äußeren Klasse

Member-Klasse
ist Subklasse der äußeren

class Outer {
  //...
  class PathologicalMember extends Outer { /*...*/ }
  //... 
}

Die 5. Regel (siehe 8.4) und die entsprechende Anmerkung bzw. Fußnote sind Grund genug, dieses Konstrukt zu meiden.

2. Eine Klasse ist Subklasse einer Member-Klasse

Icon
Subklassen von Member-Klassen

Hier wird man mit dem Problem konfrontiert, dass die Member-Klasse unbedingt eine Instanz einer äußeren Klasse zur Anlage benötigt. Andererseits wird mit Anlage einer Subklasse von Member automatisch ein Objekt der Member0-Klasse erzeugt. Die Folgerung:

Es sind nur Konstruktoren der Subklasse erlaubt, die einen Parameter o der äußeren Klasse Outer haben. Als erste Anweisung müssen sie o.super() enthalten – wieder eine Syntaxerweiterung –, womit o das äußere Objekt der Member-Superklasse wird.

class Outer {
  class MemberC {}
}
class Pathological extends Outer.MemberC 
{
  // Pathological() {}              ergibt einen C-Fehler
Pathological(Outer o) { // ok! o.super(); // notwendig! //... } }

3. Eine äußere Klasse ist Subklasse

Zugriff von Member-Klassen auf Superklassen der äußeren Klasse

Dieser Fall kann »unverschuldet« auftreten, will sagen, er kann nicht unbedingt vermieden werden (siehe Abb. 8.6).


Abbildung
Abbildung 8.6   Zugriff auf Superklassen der äußeren Klasse

Das Problem besteht darin, an verdeckte oder überschriebene Methoden der Superklassen der äußeren Klasse Outer heranzukommen.

Outer.super:
Member-Zugriff auf Superklassen von Outer

gp  Die Syntaxerweiterung Outer.super gestattet den Zugriff auf eine Superklasse von Outer.

Beispiel (zu Abb. 8.6)

class B1 { 
  protected int i= 1; 
  protected float f= 1.23f; 
}
class B2 extends B1 { 
  protected int i= 2; 
}
class Outer extends B2 {
  private int i= 3;        // verdeckt i in Superklasse
  private float f= 3.21f;  // verdeckt f in Superklasse
  class MemberC {
    void foo() {
      System.out.println(Outer.this.i +" "+Outer.this.f); // :: 3 3.21
      System.out.println(Outer.super.i+" "+Outer.super.f); 
// :: 2 1.23
    }
  }
}

Die Ausgabe wird z.B. erzeugt durch die Anweisung:

   new Outer().new MemberC().foo();

Galileo Computing

8.4.3 Member-Klasse vs. Instanz-Variable  downtop

Icon

Member-Klassen konkurrieren mit Instanz-Variablen, da sie Ähnlichkeiten aufweisen. Deshalb ist eine klare Abgrenzung wichtig, die einen Hinweis auf den unterschiedlichen Einsatz gibt.

Feld: gerichtete Aggregation, Zugriff über Feld-Schnittstelle

gp  Instanz-Variable: Sie implementiert eine gerichtete Aggregation, wobei das äußere Objekt nur Zugriff auf das enthaltene Objekt über dessen öffentliche Schnittstelle hat. Das enthaltene Objekt hat dabei keinen Zugriff auf das äußere Objekt.

Member-Klasse:
gerichtete Aggregation, voller Zugriff auf Outer

gp  Member-Klasse: Sie implementiert eine gerichtete Aggregation oder Komposition, wobei das enthaltene Objekt vollen Zugriff auf das äußere Objekt hat (siehe auch Abb. 8.4). Das äußere Objekt hat implizit keinen Zugriff auf das enthaltenen Objekt.

Aggregation mit Member-Klassen

Aggregation mit Member-Klassen

In Abschnitt 4.2.5 wurde bereits auf eine Komposition mit Hilfe innerer Klassen verwiesen.

Der Einsatz von Member-Klassen setzt aber voraus, das ein voller Zugriff des aggregierten Objekts auf das Aggregat-Objekt sinnvoll ist.

Da ein Aggregat Zugriff auf seine enthaltenen Objekte haben sollte, sind explizite Referenzen für eine bidirektionale Navigation erforderlich.

Beispiel

Aggregation bzw. Komposition:
Member-Klasse vs. Feld

Vergleich von Komposition mit Hilfe einer inneren Klasse und Aggregation mit Hilfe eines Felds (Referenz auf eine Top-Level-Klasse):

class TopLevelC {
  private int num;
  public int numGets() { return num++; }
}
class AggregateC {
  private TopLevelC t1,t2;
  private MemberC   m;
  private class MemberC { private String s; }
  public void setTopLevelC(TopLevelC t) { 
    if (t1==null) t1=t; else t2=t; 
  }
  public void createMember(String s) { (m= new MemberC()).s= s; }
  public void printAggregate() { // Kontrakt: keine 
null-Felder
    System.out.println(t1.numGets()+" "+t2.numGets()+" "+m.s);
  }
}

Im Code-Fragment werden zwei Kompositionen und eine Aggregation erzeugt:

    AggregateC a= new AggregateC();
    TopLevelC  t= new TopLevelC();
    a.setTopLevelC(t);                   // Aggregation!      ¨
    a.setTopLevelC(new TopLevelC());     // Komposition!      ‚
    a.createMember("member");            // Komposition!      ƒ
    a.printAggregate();                  // :: 0 
0 member
    System.out.println(t.numGets());     // :: 1
    a.printAggregate();                  // :: 2 1 member

Erklärung: Mit dem expliziten Setzen einer Komponente t in Zeile ¨ kommt nur eine Aggregation zu Stande, da t von beliebigen anderen Objekten außerhalb des Aggregats manipuliert werden kann.

Icon

Zeile ‚ hat dagegen eine Client-Konvention eingehalten:

Komposition durch Client-
Konvention

gp  Komposition auf Basis von Feldern wird durch Clients dadurch erzeugt, dass keine Referenzen auf die übergebenen Komponenten gehalten werden. Es existierten nur Referenzen im Aggregat.

Private Members: Komposition per Design

Wird eine Member-Klasse private deklariert und keine Referenz nach außen geliefert, so erhält man dagegen automatisch eine Komposition. Somit erzeugt die Zeile ƒ eine Komposition.


Galileo Computing

8.5 Lokale Klassen  downtop

Lokale Klassen werden wie lokale Variablen in einem Block deklariert und sind dann nur in diesem Block gültig.

Blöcke für lokale Klassen sind in der Regel Methoden, selten Instanzierer oder statische Initialisierer. Der Einfachheit halber formulieren wir die Aussagen nur für Methoden.

Lokale Klassen:
Hybride aus lokaler Variable und Klasse

Je nachdem, ob eine lokale Klasse in einer statischen oder Instanz-Methode deklariert ist, verhält sie sich wie eine lokale Variable ohne Bindung an eine Instanz oder aber wie eine Hybride aus lokaler Variable und Member-Klasse (siehe auch Abb. 8.1).

Restriktionen für lokale Klassen
Icon

Die Regeln für lokale Klassen setzen sich aus denen für lokale Variablen und denen innerer Klassen zusammen:

1. Eine lokale Klasse ist nur in dem Block gültig, in dem sie deklariert ist und kann weder private, protected, public noch static sein.
2. Lokale Klassen haben Zugriff auf final deklarierte lokale Variablen oder Parameter, die in ihrem Gültigkeitsbereich liegen.
3. Lokale Klassen können bis auf Konstanten (static final) keine statischen Felder oder Methoden besitzen.
4. Ist eine lokale Klasse in einer
    gp  statischen Methode deklariert, hat sie Zugriff auf alle statischen Felder und Methoden der Klasse.
    gp  Instanz-Methode deklariert, gelten zusätzlich die fünf Regeln zu Member-Klassen in 8.4.

Lokale Klassen sind einfach zu benutzen

Lokale Klassen sind einfacher zu verwenden als Member-Klassen, da sie außerhalb des Blocks und damit auch außerhalb der Klasse nicht direkt benutzbar sind. Dadurch entfallen viele pathologische Nebeneffekte der Member-Klassen und auch die Zugriffsnotation (8.4.1) ist überflüssig.

Einsatzgebiet von lokalen Klassen

Das Einsatzgebiet von lokalen Klassen ist weit gesteckt:

gp  Lokale Klassen sind ideal für Instanzen geeignet, die nur innerhalb einer Methode benötigt werden.
gp  Instanzen von lokalen Klassen können »undercover« außerhalb der Methode, in der die lokale Klasse deklariert ist, verwendet werden (siehe hierzu 8.5.1).

Beispiele

Es werden die beiden Varianten für lokale Klassen anhand einer Klasse Outer demonstriert. Zuerst die Anlage einer lokalen Klasse in statischer Methode:

Lokale Klassen in einer statischen Methode

class Outer {
  // Zugriff von Local über Outer.s
private static String s="O";
  static void methodWithLocalClass (final String 
CS,int k) {
    // Local hat keinen direkten Zugriff 
auf k, deshalb
final int CI= k; ¨
    class Local {
      String s= "L";
      void f(String s, int i) {
        // die Ausgabe ergibt sich durch Zeile ¦
        System.out.println(Outer.s +this.s+s+i);  // :: 
OLf1
        // Ausgabe z.B. durch Outer.methodWithLocalClass("m",2);
        System.out.println(CS+CI);                // :: m2
      }
    }
    new Local().f("f",1);                                      Æ
  }
}

Erklärung zu ¨: Um auf nicht final erklärte Parameter der Methode zugreifen zu können, in der die lokale Klasse enthalten ist, muss man den Umweg über eine zusätzlich final deklarierte lokale Variable gehen.

Lokale Klasse in einer Instanz-Methode

Es folgt die fast analoge Instanz-Variante einer lokalen Klasse:

class Outer {
  // Local hat Zugriff auf Instanz-Feld 
über Outer.this.s
private String s="O";
  void methodWithLocalClass (final String CS, int 
k) {
    final int CI= k;              
    class Local {
      String s= "L";
      void f(String s, int i) {
       System.out.println(Outer.this.s+ this.s+s+i); // 
:: OLf1
       System.out.println(CS+CI);                   // :: m2
      }
    }
    new Local().f("f",1);
  }
}

Eine zugehörige Testanweisung ist dann z.B.:

   new Outer().methodWithLocalClass("m",2);

Galileo Computing

8.5.1 Gültigkeit vs. Lebensdauer  downtop

Alle lokalen Variablen werden außerhalb des Blocks, in dem sie deklariert sind, ungültig, d.h. stehen nicht mehr im Zugriff.

Mit dem Verlust der Gültigkeit (scope) verlieren lokale primitive Variablen auch ihre Existenz. Im Allgemeinen stimmen also Gültigkeit und Lebensdauer überein.

Instanzen lokaler Klassen als Resul-tat der Methode

Dies gilt aber nicht unbedingt für Instanzen von lokalen Klassen, denn sie können außerhalb des Blocks als Objekte eines anderen Typs »überleben«.

Scope vs. Existenz

gp  In diesem Fall ist also die Lebensdauer von lokalen Objekten größer als ihre Gültigkeit.

Beispiel

In der folgenden Klasse Outer liefert die Methode stillAlive() ein lokales Objekt l als Typ B nach außen. Die lokale Klasse Local ist dabei Subklasse von B.

Die Klasse bzw. der Typ Local ist außerhalb der Methode ungültig, aber die Instanz l überlebt außerhalb als Typ B.

Lokale Klassen-Instanz überlebt als Resultat eines anderen Typs

abstract class B { abstract String 
f(); }
class Outer {
  B stillAlive (final String s) {
    final int i= 10;
    class Local extends B {
      String f() { return s+" "+i; }
    }
    Local l= new Local();
  // diese lokale Instanz von Local muss 
durch Übergabe
// an die Referenz B weiterhin existent bleiben.
return l; } }

Ein zugehöriger Testcode:

B b= new Outer().stillAlive("Wert");    System.out.println(b.f()); 
                   // :: Wert 10

Hinweis zur final-Regel in 8.5

Kopien lokaler Variablen in Instanzen lokaler Klassen

Zusammen mit einer lokalen Instanz, die nach außen geliefert wird, müssen eventuell lokale Variablen bzw. Parameter ebenfalls als Kopie überleben.

Dies ist der Grund, warum mit Hilfe von final (2. Regel in 8.5) sichergestellt werden muss, dass sich nach dem Kopiervorgang nichts mehr ändert.

Denn ansonsten müßte man Original und Kopie der Variablen bzw. Parameter im Wert immer wieder abgleichen, eine Alternative, die die Java-Sprach-Designer nicht unbedingt attraktiv fanden.


Galileo Computing

8.6 Anonyme Klassen  downtop

Anonyme Klassen sind lokale Klassen ohne Namen. Als so genannte One-Shot-Klassen ist ihre Definition unmittelbar mit der Anlage einer einzigen Instanz verbunden.

Einen idealen Einsatz finden sie bei der Implementierung von Interfaces mit nur einer, höchstens zwei Methoden.

Sie unterliegen denselben Regeln wie lokale Klassen, sind aber aufgrund ihrer Namenslosigkeit weiter eingeschränkt:

Icon
Deklaration anonymer Klassen

gp  Die Deklaration einer anonymen Klasse und die Anlage der einzigen zugehörigen Instanz sind untrennbar miteinander verbunden.
gp  Die Deklaration ist keine Anweisung, sondern ein Ausdruck.
gp  Die Deklaration erlaubt kein extends oder implements und erfolgt alternativ durch:
    gp  new SuperClassName ([arguments]) { ... }
wobei die Superklasse einen Konstruktor haben muss, der eine zu den Argumenten passende Signatur hat.
    gp  new InterfaceName() { ... }
wobei die anonyme Klasse das Interface vollständig implementieren und direkte Subklasse von Object sein muss. Konstruktor-Argumente sind also nicht erlaubt.

Icon

Zusätzlich zu den Regel für lokale Klassen gelten folgende Regeln:

Restriktionen für anonyme Klassen

1. Eine anonyme Klasse kann keine eigenen Konstruktoren definieren, sondern muss – sofern notwendig – einen Initialisierer verwenden.
2. Zusätzliche Methoden, die nicht in der Superklasse bzw. im Interface deklariert sind, können nicht von außen aufgerufen werden.7

Beispiele

Zu einer abstrakten Klasse Object3D soll eine Zylinder- bzw. Würfel-Instanz als anonyme Klasse erschaffen werden, deren Methode volume() anschließend in test() ausgegeben wird.

Zuerst die Definition der abstrakten Klasse:

abstract class Object3D {
  protected Object spec;
  public Object3D (Object spec) { 
    this.spec= spec; 
  }
  public abstract double volume(); 
     // zu implementieren
  public static void test(Object3D o3D) {
    System.out.println(o3D.volume());
  }
}

In den beiden anonymen Klassen innerhalb von Test wird die abstrakte Methode volume() implementiert:

public class Test {
  public static void main(String[] args) {

Anonymer Klassen-Ausdruck als Subklasse

  // Anlage und Test eines anonymen Zylinders
// Zuweisung zu einer Referenz
Object3D zylinder= // Konstruktor benötigt ein Object-Argument new Object3D( new double[] {2.,1./Math.PI} ) // Start der Definition
{ public double volume() { double r= ((double[])spec)[0]; double h= ((double[])spec)[1]; return Math.PI*r*r*h; } }; // ein Ausdruck ist mit Semikolon abzuschließen
    Object3D.test(zylinder);                  // 
:: 4.0

Anonymer Klassen-Ausdruck als Argument eines Methodenaufrufs

    // Anlage und Test eines anonymen Würfels
// Verwendung direkt als Argument
Object3D.test( // :: 6.0 new Object3D( new double[] {1.,2.,3.}) { public double volume() { return ((double[])spec)[0]*((double[])spec)[1]* ((double[])spec)[2]; } });
} }

Erklärung: Der Konstruktor Object3D() mit einem Object-Parameter ist zwar unschön, aber für eine generische Verwendung (sub)optimal. Denn anonyme Klassen können keine zusätzlichen Methoden deklarieren, die von außen auf einfache Art benutzt werden könnten.

Um aber unterschiedliche 3D-Körper definieren zu können, ist der generische Parameter Object ein (unschöner) Kompromiss.

Fazit: Das Beispiel zeigt deutlich, dass anonyme Klassen wie Ausdrücke verwendet werden und deshalb einigermaßen überschaubar bleiben müssen.

Deklariert man statt einer abstrakten Klasse ein Interface IObject3D, muss der Konstruktor durch eine normale Methode set() ersetzt werden:

interface IObject3D {
  void set(Object spec);
  double volume();
}

Die anonyme Klasse muss nun zwei Methoden implementieren, und ihre Instanz muss ohne Konstruktor erst einer Referenz zugewiesen werden, über die dann Werte gesetzt werden und die Ausgabe erzeugt wird.

Anonyme Klasse als Implementation eines Interfaces

public class Test {
  public static void main(String[] args) {
    IObject3D zyl=
      new IObject3D() {
        private double r=1, h=1;
        // dieses set() ist nicht im Interface 
definiert. Kann
// normalerweise nur intern benutzt werden (siehe 8.6.1)
public void set (double r, double h) { this.r= r; this.h=h; }
        public void set (Object spec) {
          set(((double[])spec)[0],((double[])spec)[1]);
        }
        public double volume() { return Math.PI*r*r*h; 
}
      };
    // zyl.set(2.,1./Math.PI); C-Fehler, 
leider nicht möglich!
zyl.set(new double[] {2.,1./Math.PI}); System.out.println(zyl.volume()); // :: 4.0 } }

Icon

Design-Hinweise für anonyme Klassen

Einsatz von anonymen Klassen

gp  Die zugehörige Superklasse bzw. das zugehörige Interface hat nur wenige abstrakte Methoden, deren Implementierung kurz ist.
gp  Es wird nur eine Instanz benötigt, wobei der Name der Klasse völlig unwichtig ist.
gp  Diese Instanz hat dieselbe Schnittstelle wie die Superklasse bzw. das Interface, benötigt also keine zusätzlichen Methoden.

Die letzte Aussage basiert auf dem Fakt, dass anonyme Instanzen nach außen immer nur als Objekte der Superklasse bzw. des Interfaces angesehen werden, was eine schöne Überleitung zum nächsten Abschnitt ist.


Galileo Computing

8.6.1 Zugriff auf interne Methoden anonymer Klassen  downtop

Wie bereits in 6.6.2 angemerkt, sind Superklassen und Interfaces keine Firewalls.

Kennt man den wahren Typ des Objekts, welches sich hinter dem Interface bzw. der Superklasse verbirgt, kann man auf seine speziellen Eigenschaften bzw. Methoden zugreifen, ob der Class-Provider dies nun will oder nicht.

Nun könnte man sich zumindest für anonyme Klassen auf die zweite Regel in 8.6 verlassen, die besagt, dass zusätzliche Methoden, die nicht in der Superklasse oder im Interface definiert sind, vor äußerem Zugriff geschützt sind.

Probleme beim Export von inneren Klassen als Interface

Diese Regel eignet sich somit vorzüglich, um Klassen als Interface-Objekte aus der äußeren Klasse zu exportieren, ohne dass der Client die Klasse selbst benutzen kann (siehe hierzu 8.7).

Handeln wir nach dem Grundsatz »Vertrauen ist gut, Kontrolle ist besser«:

class Outer {
  // nutzloses Marker-Interface
private interface IUseless {}
  public static IUseless getLocal() {
    // anonyme Klasse zum Interface
return new IUseless() { String unuseable() { return "ups"; } }; } }

Die anonyme Klasse bzw. Instanz, die Outer.getLocal() exportiert, ist nutzlos, da die Methode unuseable() nach der zweiten Regel in 8.6 nicht benutzt werden kann, da sie im Interface IUseless nicht erklärt ist.

Diese Auffassung suggeriert auch der Compiler, der die folgende Anweisung mit einem Fehler quittiert:

System.out.println(Outer.getLocal().unusable()); 
// C-Fehler

Reflexions-Mechanismus zum
Aufruf von Methoden innerer Klassen

Diese Hürde kann aber locker genommen werden. Eine kleine Reflexions-Variante (siehe Kapitel 13) ergibt dann:

public class Test {
  public static void main(String[] args) {
   // der nachfolgende Code führt 
zu dem Aufruf von
// System.out.println(Outer.getLocal().unusable());
    Object o= Outer.getLocal();
    Method[] useable= o.getClass().getDeclaredMethods();
    try {
      System.out.println(useable[0].invoke(o,null));  // 
:: ups
    } catch(Exception e) {}
  }
}

Galileo Computing

8.6.2 Probleme mit anonymen Klassen  downtop

Im nächsten Beispiel reagieren zwei bekannte Entwicklungsumgebungen JBuilder 3 bzw. 4 und Forte auf einen einfachen Testcode recht unterschiedlich.

Anonyme Klassen können zu Compiler-bzw. Laufzeit-Problemen führen

JBuilder 3 reagiert bei fehlerhaftem Code nicht immer mit einem Compiler-Fehler, sondern überlässt dies der JVM, die sich mit einer kryptischen VerifyErrorException verabschiedet. Der Forte-Compiler ist dagegen sehr konservativ und lehnt dafür auch korrekten Code ab. JBuilder 4 liefert dagegen zutreffende Ergebnisse.

Beispiel: Compiler- bzw. JVM-Test

Auf die Variable i in der folgenden Klasse Fubar sollte eine anonyme Klasse zugreifen können, wenn i nicht private deklariert ist.

abstract class Fubar {
  private int i= 1; // 1. Version mit, 2. 
Version ohne private
  abstract int f();
  static void test() {
    // Anlage einer anonymen Klasse
Fubar fb= new Fubar() { int f() { return i; } }; } }

Der Testcode besteht nur aus dem Aufruf:

    Fubar.test();

Ausführung der 1. Version (mit private):

Vorsicht: Irreführende Ergebnisse/Meldungen bei Entwicklungsumgebungen

JBuilder 3: Exception in thread "main" java.lang.VerifyError...
Accessing value from uninitialized register 0

Forte: Compiler-Fehler Invalid use of "this" selector syntax...

Ausführung der 2. Version (ohne private):

JBuilder 3 und 4: Kein Fehler!

Forte: Compiler-Fehler Invalid use of "this" selector syntax...


Galileo Computing

8.7 Firewall-Idiom: Quasi-Objekte von Interfaces  downtop

Innere Klassen als Quasi-Objekte von Interfaces

Besonders interessant ist der Einsatz von inneren Klassen zur Umsetzung des Template-Prinzips beim Interface-Pattern (siehe 6.6), um konkrete Klassen-Implementierungen zu schützen.


Abbildung
Abbildung 8.7   Klassen-Schablone für Quasi-Objekte von Interfaces

Icon
Äußere Klasse als Factory und Firewall

gp  Die äußere Klasse spielt dabei die Rolle einer Factory-Klasse und eines Firewalls, indem sie Instanzen von inneren Klassen nur als Interface exportiert (siehe Abb. 8.7).10 

Die Firewall muss verhindern, dass die exportierten Instanzen nicht anders als im Interface definiert verwendet werden.

Beispiel

Exportieren von statischen inneren Klassen als Interface-Objekte

Die folgende Klasse FirewallFactory liefert aufgrund eines Selektors drei verschiedene Implementierungen für den IService aus:

Schutz vor unerlaubtem Cast

class FirewallFactory {
  public interface IService { // alternativ top-level
    void anyService();
  }
  // dieses private schützt vor unerlaubtem Cast
private static class ProtectedStaticCService implements IService { public void anyService() { internalMethod(); }

Schutz vor
Reflexion

    // dieses private schützt vor Aufruf 
mittels Reflection
private void internalMethod() { System.out.println("static: internalMethod()"); } }
  static public IService createIService(int 
selector) {
    class ProtectedLocalCService implements IService {
      public void anyService() { internalMethod(); }
      private void internalMethod() {
        System.out.println("static local: internalMethod()");
      }
    }
    switch (selector) {
      case 0:  return new IService() {
                  public void anyService() { internalMethod(); }
                  private void internalMethod() {
                    System.out.println(
                       "static anonym: internalMethod()");
                  }
               };
      case 1:  return new ProtectedStaticCService();
      case 2:  return new ProtectedLocalCService();
      default: return null;
    }
  }
}

Erklärung: Da die Klasse FirewallFactory nur zur Erzeugung des Services IService dient, kommen eigentlich nur statische innere Klassen in Frage.

Sollte die Klasse keine reine Factory darstellen, sondern Teil des Services sein, sind auch nicht statische Varianten sinnvoll.

Firewall:
Cast und Aufruf über Reflexion werden verhindert

In diesem »konstruierten« Fall werden die drei Möglichkeiten statische innere, lokale oder anonyme Klasse implementiert und wahlweise als Quasi-Objekt IService dem Client übergeben.

Der Client kann zwar weiterhin die wahre Klasse hinter IService mittels Reflection ermitteln (unsichtbar werden sie also nicht!), aber ein Cast zu dieser Klasse bzw. ein Aufruf der internen Methoden ist aufgrund der private-Modifikatoren ausgeschlossen.

Nachfolgend ein kurzer Test, bei dem der Client nicht nur die normale IService-Methode anyService() aufrufen will, sondern mittels Type-Cast das Objekt direkt benutzen oder per Reflection die (jeweils zweite) interne Methode internalMethod() ausführen will.

Dies führt dann aber entweder zu einem Compiler-Fehler oder zu einer IllegalAccessException, wie nachfolgend demonstriert.

public class Test {
  public static void main(String[] args) {
    /* Versuch eines Casts führt zu Compiler-Fehler
FirewallFactory.ProtectedStaticCService pss= (FirewallFactory.ProtectedStaticCService) FirewallFactory.createIService(1); */
for (int i=0; i<3; i++) { FirewallFactory.IService ffs= FirewallFactory.createIService(i); ffs.anyService(); // nähere Einzelheiten zu Reflection siehe 13. Kapitel
Method[] m= ffs.getClass().getDeclaredMethods(); try { // Aufruf von internalMethod() führt zu einer Ausnahme
m[1].invoke(ffs,null); } catch(Exception e) { System.out.println(e); } } } }

Galileo Computing

8.8 Generisches Verhalten  downtop

Generisches Verhalten durch Plug-in-Funktionen/Methoden

Jeder C++-Programmierer kennt Funktions-Zeiger (Pointer) als Parameter, um das Verhalten einer Funktion so generisch wie möglich zu gestalten. Das Verhalten ist dann von dem Funktions-Argument (plug-in function) abhängig, das zur Laufzeit »eingehängt« wird.

Da Java keine globalen Methoden – losgelöst von Objekten – kennt, ist eine direkte Nachbildung nicht möglich. Jedoch gibt es besonders mit Hilfe der anonymen Klassen ein Äquivalent, welches typsicher und elegant ist.


Galileo Computing

8.8.1 Pluggable-Behavior-Pattern  downtop

Interface mit Plug-in-Methode(n)

Eine Methode pugInMethod() wird in ein Objekt »eingehüllt«, welches von einer abstrakten Klasse oder – besser noch – von einem Interface IBehavior abgeleitet wird, die/das diese Methode definiert (Abb. 8.8).

Icon


Abbildung
Abbildung 8.8   Variante des Pluggable-Behavior-Pattern

Pluggable-Behavior-Pattern

Die Methode pluggable() einer Klasse PluggableBehavior kann nun dynamisch ihr Verhalten mit Hilfe von pugInMethod()anpassen, dadurch dass sie einen generischen IBehavior-Parameter enthält.

Vorteil

Nun können einzelne oder alle Instanzen der Klasse PluggableBehavior unterschiedliches Verhalten haben, abhängig davon, welches Objekt der Methode pluggable() als Argument übergeben wird.

Die Einsatzgebiete sind sehr umfangreich, was neben der Sprachunabhängigkeit den Namen Pluggable-Behavior-Pattern rechtfertigt.

Allerdings wird diese Lösung auch als spezielle Variante des fundamentalen Adapter-Patterns (siehe 9.12.3) gehandelt.

Beispiel

Comparator:
ein Beispiel für Pluggable Behavior

Im Folgenden wird ein Array von Personen mit Hilfe von anonymen Plug-in-Objekten sortiert. Hierzu wird das Interface Comparator benutzt, das im Collection-Framework als eine pluggable Sortier- bzw. Ordnungs-Methode verwendet wird (siehe Abb. 8.9).


Abbildung
Abbildung 8.9   Generische Methode sort() mit Plug-in compare()

Anonyme Klassen sind ideale Plug-in-Methoden

public class Test {
  public static void main(String[] args) {
    Person[] parr= { Person.getPerson("Maier","17.10.1971"),
                     Person.getPerson("Schmidt","15.01.1956"),
                     /*...*/  };
    // sortiert aufsteigend nach dem Namen der Person
Arrays.sort(parr,new Comparator() { public int compare(Object o1, Object o2) { return ((Person)o1).getName().compareToIgnoreCase( ((Person)o2).getName()); } }); // sortiert aufsteigend nach dem Alter der Person
Arrays.sort(parr,new Comparator() { public int compare(Object o1, Object o2) { return -((Person)o1).getBirthday().compareTo( ((Person)o2).getBirthday()); } }); } }

Abschließend wird eine immutable Klasse Person angelegt:

Design-Studie:
immutable Person mit private Konstruktor

class Person {
  private static DateFormat df= 
             DateFormat.getDateInstance(DateFormat.MEDIUM);
  static final Person INVALID_PERSON;
  private String name;
  private Date birthday;

Problem bei static final Initialisierung
(JBuilder 3)

  static {
  // eine direkte Zuweisung der final INVALID_PERSON
// in try-catch lässt der Compiler nicht zu (s.u.)
Person p= null; try { p= new Person("@",df.parse("01.01.1")); } catch (Exception e) {} finally { INVALID_PERSON= p; } }
  // direkte Anlage einer Person nicht erlaubt
private Person(String s, Date d) { name=s; birthday= d; };
  public static Person getPerson (String name, 
                                  String birthday) {
    try { return new Person (name,df.parse(birthday)); }       ¨
    catch (ParseException e) { return INVALID_PERSON; }
  }
  public String getName()   { return name; }
  public Date getBirthday() { return (Date)birthday.clone();}
  public String getBirthdayString()
     { return df.format(birthday); }
}

Erklärung: Leider kann innerhalb des statischen Initialisierers im try-catch die Referenz INVALID_PERSON nicht direkt gesetzt werden, der Compiler sieht dies als womöglich doppelte Initialisierung an.

Da der Konstruktor private ist, kann keine Instanz direkt, sondern nur über getPerson() angelegt werden. Damit steht die Anlage unter voller Kontrolle der Klasse.

Fehlerhafte Personen-Anlagen werden nicht mit einer Ausnahme bestraft, sondern mit einer Referenz auf INVALID_PERSON.

zu ¨: Damit die Person immutable bleibt, wird bei getBirthday() nicht die Original-Referenz, sondern ein Klon exportiert.


Galileo Computing

8.9 Zusammenfassung  downtop

Innere Klassen sind für den praktischen Einsatz konzipiert. Denn Top- Level-Klassen auf Package-Ebene lassen keine enge Bindung von Klassen zu.

Innere Klassen lassen eine weitere Strukturierung und Verteilung des Services einer Hauptklasse zu, ohne dass die Hilfsklassen isoliert von der Hauptklasse im Package deklariert und sichtbar werden.

Abhängig von dem Problem hat der Entwickler erst einmal die Wahl zwischen statischen und nicht statischen inneren Klassen.

Hat die Hilfsklasse Aufgaben nur auf Klassen-Ebene zu verrichten, ist eine der statischen Varianten sinnvoll. Hat dagegen die innere Klasse Instanzen der Hauptklasse zu unterstützen, ist eine nicht statische Variante zu wählen.

Die statischen inneren Klassen sind einfacher zu verstehen. Member-Klassen benötigen eine neue Syntax und sind in der Handhabung sicherlich komplexer.

Alle inneren Klassen weisen eine nützliche neue Eigenschaft auf, da sie Zugriff auf die Interna der äußeren Klasse, d.h. auf deren private deklarierte Felder haben.

Im Gegensatz zu der normalen Aggregation von Feldern in äußeren Klassen ist die Zugriffsrichtung vertauscht.

Member-Klassen lassen Kompositionen einfacher zu, die ansonsten auf Client-Server-Konventionen beruhen.

Durch die nachträgliche Einführung in die Java-Sprache treten bei Ausnutzung aller Möglichkeiten Effekte auf, die schwierig zu verstehen sind, und zu einem unklaren Design führen. Unangenehm wird es dann, wenn – wie in einem Beispiel demonstriert – verschiedene Compiler zu unterschiedlichen Ergebnissen kommen.

Als Letztes wurden ein Firewall-Idiom und das Pluggable-Behavior-Pattern anhand kurzer Beispiele vorgestellt.


Galileo Computing

8.10 Testfragen  toptop

Zu jeder Frage können jeweils eine oder mehrere Antwort(en) bzw. Aussage(n) richtig sein.

1. Welche Aussagen sind richtig?

2. Es ist folgende Definition gegeben:

class Outer {
  private int i;
  static class Inner1 { /*...*/ }                ¨
  class Inner2 { /*...*/ }                       ¦
  void m() {  class Inner3{ /*...*/ } }          Æ
}

Welche Aussagen sind richtig?

3. Es ist folgende Applikation gegeben:

interface I {  int f(); }
class Outer {
  private int i= 1;
  public static class Inner1 { public 
int j= 2;  }
  public class Inner2 {
    public void f() { System.out.println("Inner2"); }
  }
  public I m() {
    return new I() {
      public int f() {  
         return 3;                                            ¨
        // statt 3 soll die Summe von i+j der Klasse 
        // Outer bzw. Inner1 zurückgegeben werden  
      }
    };
  }
}
public class Test {
  public static void main(String[] args) {
    System.out.println(new Outer().Inner1().j);               ¦
    System.out.println(new Outer.Inner1().j);                 Æ
    System.out.println(new Inner1().j);                       Ø
    System.out.println(Outer.new Inner1().j);                 ×
    new Outer().new Inner2().f();                             ±
    new Outer().Inner2().f();                                 ð
  }
}

Welche Aussagen bzw. Anweisungen sind richtig?

4. Es ist folgende Definition gegeben:

class OuterC {
  private int i=1;
  public static class Inner1 extends OuterC {}            ¨
  public class Inner2 { public int f() { return i; }  }
  public OuterC m() {
    return new OuterC() {                                 ¦
      private int i= 3;
      public int f() { return i; }
    };
  }
  public int f() { return i; }
  public static void main() {
    OuterC o= new OuterC();
    System.out.print(new Inner1().f());
    System.out.print(o.new Inner2().f())                  Æ
    System.out.print(o.m().f());                          Ø
  }
}
public class Test {
  public static void main(String[] args) { 
    OuterC.main();  
  }
}

Welche Aussagen sind richtig?

A: Die Ausgabe ist: 113

B: Die Zeile ¨ erzeugt einen Compiler-Fehler.

C: Die Zeile ¦ erzeugt einen Compiler-Fehler.

D: Die Zeile Æ erzeugt einen Compiler-Fehler.

E: Die Zeile Ø erzeugt einen Compiler-Fehler.

5. Es ist folgende Definition gegeben:

class C1 {
  class C2 {
    C1 c1;
    C2(C1 c1) {
      System.out.print("C2"); 
      this.c1=c1;
    }
  }
  C1() {
    System.out.print("C1");
  }
  void f() {
    new C1().new C2(this);                      ¨
  }
}
public class Test {
  public static void main(String[] args) {
    new C1().f();
  }
}

Welche Aussagen sind richtig?

A: Die Ausgabe ist: C2C1C1

B: Die Ausgabe ist: C1C2

C: Die Ausgabe ist: C1C1C2

D: Die Zeile ¨ erzeugt einen Compiler-Fehler.






1    Schachtelung führen aber zu obskuren Klassen-Konstrukten, die nur dazu dienen, Regeln, Compiler und die Umsetzung auf das externe Dateisystem zu testen.

2    Die Umkehrung gilt nicht, da dies zu einer zyklischen Ableitung führen würde.

3    Eine gleichzeitige Member- und Subklassen-Beziehungen ist nur pathologisch zu nennen, da dann eine Instanz der Member-Klasse einerseits mit einer Instanz der
äußeren Klasse über die Member-Beziehung und andererseits mit einer anderen
Instanz über die Vererbungs-Beziehung verbunden ist.

4    Sollte dies alles nicht klar sein, ein Hinweis: Konstrukt meiden. Sollte es klar sein: Konstrukt trotzdem meiden.

5    Außer es enthält eine Referenz auf das äußere Objekt (bidirektionale Assoziation!).

6    Außer es enthält eine Referenz auf das enthaltene Member-Objekt.

7    Diese Regel kann getunnelt werden, d.h. ist eine »Hinz- und Kunz«-Regel (siehe 8.6.1).

8    Sun Forte for Java CE 1.0, Release 2.

9    Auf private i kann nicht zugegriffen werden, da test() static deklariert ist.

10    Denn es wird immer wieder Anwender/Clients geben, die das Template-Prinzip
ignorieren und so versuchen, direkt auf die konkrete Klasse zuzugreifen, mit dem Effekt, dass sie bei Änderung der Implementation recht beleidigt sind.

  

Perl – Der Einstieg




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken und speichern. 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.
Die Veröffentlichung der Inhalte oder Teilen davon bedarf der ausdrücklichen schriftlichen Genehmigung von Galileo Press. Falls Sie Interesse daran haben sollten, die Inhalte auf Ihrer Website oder einer CD anzubieten, melden Sie sich bitte bei: stefan.krumbiegel@galileo-press.de


[Galileo Computing]

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