Advanced   Java   Services Anonyme Klassen Back Next Up Home


Anonyme Objekte versus anonyme Klassen

Zusammen mit den inneren Klassen, die ab der Version 1.4 des J2SDK nested statt inner genannt werden, wurden in Java auch sogenannte anonyme Klassen eingeführt. Eine anonyme Klasse ist eine Klasse ohne Namen, genauer gesagt, eine Klasse ohne eigenen Namen. Bisher kennen wir nur anonyme Objekte. Braucht man etwa eine bestimmte Farbe für eine Komponente nur ein einziges Mal, so kann man der Methode setBackground() eine namenlose Referenz auf ein Objekt übergeben.

Statt

Color col = new Color(220, 230, 240) ;
comp.setBackground(col);

schreibt man gerne kürzer

comp.setBackground( new Color(220, 230, 240) );

Hier wird ein Objekt vom Typ Color angelegt, das keinen eigenen Namen hat.

Eine anonyme Klasse existiert nicht als eigenständiges Gebilde, von dem man irgendwann Objekte anlegen kann. Schreibt man eine anonyme Klasse, so muß zu dieser sofort ein Objekt angelegt werden. Die Syntax erlaubt keine spätere Initialisierung.

Aus dem letzten Punkt ergibt sich nebenbei, daß eine anonyme Klasse nicht abstrakt sein kann.


Anonyme Klasse ist Ableitung einer gegebenen Klasse

Wie sieht das nun konkret aus? Dazu geben wir uns zunächst irgendeine Klasse vor, zu der wir eine anonyme Klasse bilden wollen, etwa die folgende:

class IrgendEineKlasse
{
   private int a ;

   public IrgendEineKlasse()
   {
      System.out.println("Konstruktor IrgendEine() ");
   }

   public IrgendEineKlasse(int x)
   {
      a = x ;
      System.out.println("Konstruktor IrgendEine(int x)");
   }

   public void println()
   {
      System.out.print(a);
   }
}

Zu dieser Klasse wollen wir nun eine anonyme Klasse bilden. Das geschieht nach dem folgenden Schema:

IrgendEineKlasse iek = new IrgendEineKlasse() { /*anonyme Klasse*/ } ;

Innerhalb der geschweiften Klammern kann man nun eine Klassen entwerfen, indem man die üblichen Bestandteile einer Klasse ergänzt. Die anonyme Klasse wird also zusammen mit einem Konstruktoraufruf realisiert. Es muß dabei nicht der Defaultkonstrukotr verwendet werden. Die anonyme Klasse besitzt aber nur eingeschränkte Möglichkeiten. Da die anonyme Klasse keinen eigenen Namen hat, kann sie weder statische Daten noch statischen Methoden enthalten. Das Fehlen eines eigenen Namens hat aber noch weitere Konsequenzen. Schreibt man in der anonymen Klasse neue Methoden, so kann man diese nur innerhalb der anonymen Klasse verwenden. Da sich das Objekt den Typ der Elternklasse geliehen hat (leihen muß!), kann es die neuen Methoden der anonymen Klasse nicht kennen. Entsprechendes gilt für den Datenteil einer anonymen Klasse. Da ein Konstruktor immer den Namen der eigenen Klasse trägt, kann eine anonyme Klasse logischerweise auch keinen Konstruktor besitzen. Statt dessen gibt es einen namenlosen anonymen Konstrukor oder anonymous initializer, wie das folgende Beispiel zeigt. Eine anonyme Klasse wird daher oft verwendet, um eine Methode zu überschreiben, deren Wirkungsweise man (nur) temporär verändern will.

class IrgendEineAndereKlasse
{
   private IrgendEineKlasse iek = new IrgendEineKlasse(17)
                          // Beginn der anonymen Klasse
                          {
                            {
                               System.out.println("anonymer Konstruktor der anonymen Klasse");
                               System.out.println("anonymous initializer");
                            }

                            public void println()
                            {
                              anonymousPrintln();
                            }

                            public void anonymousPrintln() // kann nicht von iek verwendet werden
                            {
                              super.println();
                              System.out.println();
                            }

                          } ; // Ende der anonymen Klasse + Semikolon !
}

Das Beispiel zeigt zunächst einen anonymen Initialisierer und dann eine Überschreibung der Methode println(). Wie man sieht, macht das Original der Methode println() im Gegensatz zum Namen keinen Zeilenvorschub. Dies wird in der anonymen Klasse korrigiert. Die überschreibende Methode println() ruft die Methode anonymousPrintln(), die den Zeilenvorschub nachliefert. Die Methode anonymousPrintln() kann zwar innerhalb der anonymen Klasse verwendet werden, aber eben nicht vom Objekt iek, da iek den Typ der Elternklasse hat. Das Ganze läßt sich natürlich auch ohne zusätzliche Methode anonymousPrintln() kodieren, aber das ist hier nicht der Sinn des Beispiels.

Im obigen Beispiel habe wir zur anonymen Klasse ein Objekt mit einem eigenen Namen konstruiert. Oft legt man in solchen Situationen aber auch eine namenloses Objekt an. Das sieht dann wie folgt aus:

window.addWindowListener( new WindowAdapter()
                           // begin anonymous class
                           {
                             public void windowClosing(WindowEvent e)
                             {
                               System.exit(0) ;
                             }
                           }
                           // end anonymous class
                         ) ;

Hier wird in der anonymen Klasse die Methode windowClosing aus der Klasse WindowAdapter überschrieben und dazu ein namanloses Objekt angelegt.



Anonyme Klasse implementiert ein Interface und ist Ableitung von Object

Nun geben wir uns irgendein Interface vor, etwa das folgende:

public interface IrgendEinInterface
{
   void print() ;
   void println() ;
}

Hierzu wollen wir eine anonyme Klasse (und zwangsläufig ein Objekt) konstruieren. Zu einem Interface kann man nicht sofort Objekte instantiieren. Eine Zeile wie

IrgendEinInterface iei = new IrgendEinInterface() ;

führt sofort zu einer Fehlermeldung des Compilers, da ein Interface ja keinen Konstruktor besitzen kann:

IrgendEinInterface is abstract; cannot be instantiated

Wir verbessern unseren Ansatz und probieren es mit einer anonymen Klasse:

IrgendEinInterface iei = new IrgendEinInterface() { /* anonyme Klasse */ } ;

Die neuerliche Fehlermeldung bringt uns auf die richtige Spur:

<anonymous Xxx$1> should be declared abstract; it does not define print()

Es gibt also tatsächlich so etwas wie einen Konstruktoraufruf für ein Interface, vorausgesetzt, man schreibt dazu eine anonyme Klasse, die das Interface implementiert:

IrgendEinInterface iei = new IrgendEinInterface()
                             {
                               public void print()
                               {
                                 // Realisierung von print()
                               }

                               public void println()
                               {
                                 // Realisierung von println()
                               }
                             } ;

Daß es sich hier tatsächlich um eine Ableitung von Object handelt (handeln muß...) beweisen wir, indem wir zur Instanz iei eine Methode aus der Klasse Object aufrufen. Das folgende Statement ist für den Compiler kein Grund zur Beanstandung.

System.out.println( iei.hashCode() ) ;

Wie im ersten Fall kann man auch in dieser Situation mit einem namenlosen Objekt arbeiten.

button1.addActionListener( new ActionListener()
                           // begin anonymous class
                           {
                             public void actionPerformed(ActionEvent e)
                             {
                               // irgendeine action zu button1
                             }
                           }
                           // end anonymous class
                         ) ;

button2.addActionListener( new ActionListener()
                           // begin anonymous class
                           {
                             public void actionPerformed(ActionEvent e)
                             {
                               // irgendeine action zu button2
                             }
                           }
                           // end anonymous class
                         ) ;

Obige Situation findet man häufig in graphischen Oberflächen. Das ist zum einen praktisch, zum anderen mischt man hier jedoch GUI-Programmierung mit Ereignishandling. In einem guten objektorientiertem Design werden diese Teile getrennt. Hierfür bieten sich nichtstatische nested Klassen an. Wir fassen unsere bisherigen Ergebnisse noch zusammen.

Hinweis:

Es gibt immer wieder die Meinung, anonyme Klassen hätten nichts mit Vererbung zu tun. Wenn dem so wäre, müßte sich etwa die folgende Zeile anstandslos compilieren lassen.

String st = new String("Hallo") {  } ;

Probieren Sie das mal und lesen Sie die Fehlermeldung des Compilers !



Zugriff auf äußere Variablen

Es kann nur zwei Arten von äußeren Daten geben. Zum einen lokale Daten innerhalb einer Methode oder eines Konstruktors oder eines Initialisierers, in dem die anonyme Klasse auftaucht, zum anderen globale Datenmember der äußeren Klasse. Im folgenden Beispiel werden beide Situationen gezeigt.

public class UmschlieszendeKlasse
{
   int global, b ;
   static int statisch;

   public void method()
   {
      int lokal1 = 1;
      final int lokal2 = 2;

      Object ob = new Object()
                     {
                        //anonymous initializer
                        {
                           //int a = lokal1; // local variable lokal1 needs to be declared final
                           int b = 17 ;
                           int c = lokal2 ;  // ok
                           int d = global ;  // ok
                           int e = UmschlieszendeKlasse.this.b; // Auflösen des Namenskonflikts
                           statisch = 19 ;   // ok
                        }

                     } // end anonymous class
   } // end method

   public static void staticMethod()
   {
      Object ob = new Object()
                     {
                        //anonymous initializer
                        {
                           //int a = b;  // b ist keine statische Variable
                           statisch = 19 ;   // ok
                        }

                     } // end anonymous class
   } // end method

} // end class

Während die anonyme Klasse auf Datenmember zugreifen darf, kann sie lokale Daten nur verwenden, wenn diese final sind. Zu beachten ist auch, ob die anonyme Klasse in einem statischen Kontext steht. In diesem Fall kann die anonyme Klasse nur auf statische Daten zugreifen.


Noch ein Beispiel

Die Klasse TextArea verfügt über eine Methode append() zum Anhängen von Text. Diese Methode macht keinen Zeilenvorschub. Eine Methode appendln(), die einen abschließenden Zeilenvorschub macht, existiert dagegen nicht. Man könnte eine Unterklasse schreiben und eine Methode appendln() ergänzen. Man kann aber auch das Verhalten von append ändern und generell mit einen Zeilenvorschub schließen. Das kann mit Hilfe einer anonymen Klasse folgendermaßen aussehen:

TextArea tear = new TextArea()
                  {
                     public void append(String s)
                     {
                        super.append(s+"\n");
                     }
                  } ;

Braucht man mehrere Objekte mit einer so manipulierten append() Methode, so ist obige Lösung nicht geschickt, da man für jedes Objekt die Klasse neu schreiben müßte. In so einem Fall kann man mit einer statischen Methode arbeiten:

public static TextArea createTextArea()
{
   return new TextArea()
          {
            public void append(String s)
            {
              super.append(s+"\n");
            }
         } ;
}

Damit kann man dann etwa (in der gleichen Klasse) folgendes schreiben:

TextArea tear1 = createTextArea() ;
TextArea tear2 = createTextArea() ;
TextArea tear3 = new TextArea() ;

TextArea[] tears = { tear1, tear2, tear3 } ;

Ein weiteres Beispiel einer anonymen Klasse finden Sie im Kapitel Swing im Teil LineWrapping bei JTextPane.


Namen der BytecodeDateien

Der Compiler speichert jede Klasse als eigene Datei. Das gilt auch für die namenlosen anonymen Klassen. Für das einleitende Beispiel erzeugt der Compiler die folgenden Dateien:

Bettet man das Beispiel mit den beiden Buttons in eine Klasse etwa mit dem Namen AnonymousDemo ein, so erzeugt der Compiler die folgenden Dateien:

Die anonymen Klassen werden also in der Reihenfolge ihres Auftretens vom Compiler durchnumeriert.

Valid XHTML 1.0 Strict top Back Next Up Home