From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Bis zum JDK 1.0 wurden Klassen immer auf Paketebene definiert, eine
+Schachtelung war nicht möglich. Das hat die Compiler-Implementierung
+vereinfacht und die Struktur der Klassen innerhalb eines Pakets flach
+und übersichtlich gemacht. Unhandlich wurde dieses Konzept immer
+dann, wenn eine Klasse nur lokale Bedeutung hatte oder wenn »auf
+die Schnelle eine kleine Klasse« gebraucht wurde. Da es in Java
+keine Funktionszeiger gibt, besteht die einzige Möglichkeit,
+kleine Codebestandteile zwischen verschiedenen Programmteilen auszutauschen,
+darin, ein Interface zu deklarieren und die benötigte Funktionalität
+in einer implementierenden Klasse zur Verfügung zu stellen. Diese
+Technik wurde in Abschnitt 9.4.3
+vorgestellt.
+
+
+Insbesondere bei den Erweiterungen für die Programmierung grafischer
+Oberflächen, die mit dem JDK 1.1 eingeführt wurden, entstand
+der Wunsch nach einem flexibleren Mechanismus. Durch das neue Ereignismodell
+(siehe Kapitel 28)
+müssen seit dem JDK 1.1 sehr viel häufiger kleine Programmteile
+geschrieben werden, die nur in einem eng begrenzten Kontext eine Bedeutung
+haben. Die Lösung für dieses Problem wurde mit der Einführung
+von lokalen und anonymen Klassen geschaffen (im JDK werden sie als
+Inner Classes bezeichnet). Dabei wird
+innerhalb einer bestehenden Klasse X eine neue Klasse Y definiert,
+die nur innerhalb von X sichtbar ist. Objektinstanzen von Y können
+damit auch nur innerhalb von X erzeugt werden. Anders herum kann Y
+auf die Membervariablen von X zugreifen.
+
+
+Lokale und anonyme Klassen sind ein mächtiges - und manchmal
+leicht verwirrendes - Feature von Java. Wir wollen nachfolgend seine
+wichtigsten Anwendungen vorstellen. Darüber hinaus gibt es seltener
+genutzte Varianten, die hauptsächlich durch trickreiche Anwendung
+von Modifiern auf die lokale Klasse oder ihrer Member entstehen. Auf
+diese wollen wir hier nicht weiter eingehen.
+
+
+
+
+
+Die Definition einer nicht-statischen lokalen Klasse entspricht genau
+dem zuvor beschriebenen Grundprinzip: innerhalb des Definitionsteils
+einer beliebigen Klasse wird eine neue Klasse definiert. Ihre Instanzierung
+muss innerhalb der äußeren Klasse erfolgen, also in einer
+der Methoden der äußeren Klasse oder während ihrer
+Initialisierung. Die innere Klasse kann auf die Membervariablen der
+äußeren Klasse zugreifen und umgekehrt. Das folgende Listing
+illustriert dies an einem einfachen Beispiel:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 10 - OOP IV: Verschiedenes
+
+
+
+
+
+10.1 Lokale und anonyme Klassen
+
+
+
+
+
+
+
+10.1.1 Grundlagen
+
+10.1.2 Nicht-statische lokale Klassen
+
+
+
+
+Klassen in Klassen
+
+
+
+
+Listing 10.1: Eine nicht-statische lokale Klasse
+
+
+
+
+
+001 /* Listing1001.java */
+002
+003 class Outer
+004 {
+005 String name;
+006 int number;
+007
+008 public void createAndPrintInner(String iname)
+009 {
+010 Inner inner = new Inner();
+011 inner.name = iname;
+012 System.out.println(inner.getQualifiedName());
+013 }
+014
+015 class Inner
+016 {
+017 private String name;
+018
+019 private String getQualifiedName()
+020 {
+021 return number + ":" + Outer.this.name + "." + name;
+022 }
+023 }
+024 }
+025
+026 public class Listing1001
+027 {
+028 public static void main(String[] args)
+029 {
+030 Outer outer = new Outer();
+031 outer.name = "Outer";
+032 outer.number = 77;
+033 outer.createAndPrintInner("Inner");
+034 }
+035 }
+
+
+Listing1001.java
+
+Zunächst wird eine Klasse Outer +mit den Membervariablen name +und number definiert. Innerhalb +von Outer wird dann eine Klasse +Inner definiert. Sie besitzt +eine eigene Membervariable name +und eine Methode getQualifiedName. +Beim Programmstart erzeugt main +eine Instanz von Outer und initialisiert +ihre Membervariablen. Anschließend ruft sie die Methode createAndPrintInner +auf. + +
+In createAndPrintInner wird +nun eine Instanz von Inner erzeugt +und mit dem als Argument übergebenen Namen initialisiert. Die +Instanzierung erfolgt also im Kontext der äußeren Klasse, +und diese kann auf die Membervariable der inneren Klasse zugreifen. +In der Praxis wichtiger ist jedoch die Möglichkeit, die innere +Klasse auf die Membervariablen der äußeren Klasse zugreifen +zu lassen. Dadurch wird ihr der Status der äußeren Klasse +zugänglich gemacht und sie kann Programmcode erzeugen (und durch +die Kapselung in ein Objekt nötigenfalls an eine ganz andere +Stelle im Programm transferieren), der Informationen aus der Umgebung +der Klassendefinition verwendet. Um dies zu zeigen, ruft Outer +nun die Methode getQualifiedName +der inneren Klasse auf. + +
+In getQualifiedName wird auf +drei unterschiedliche Arten auf Membervariablen zugegriffen. Bei der +Verwendung von unqualifizierten Namen (also solchen ohne Klassennamen-Präfix) +werden lexikalische Sichtbarkeitsregeln angewandt. Der Compiler +prüft also zunächst, ob es eine lokale Variable dieses Namens +gibt. Ist das nicht der Fall, sucht er nach einer gleichnamige Membervariable +in der aktuellen Klasse. Ist auch diese nicht vorhanden, erweitert +er seine Suche sukzessive von innen nach außen auf alle umschließenden +Klassen. Im Beispiel bezeichnet name +also die gleichnamige Membervariable von Inner +und number diejenige von Outer. +Wird die Membervariable einer äußeren Klasse durch eine +gleichnamige Membervariable der inneren Klasse verdeckt, kann über +den Präfix »Klassenname.this.« auf die äußere +Variable zugegriffen werden. Im Beispielprogramm wird das für +die Variable name so gemacht. + +
+Wird der Ausdruck »Klassenname.this« alleine verwendet, +bezeichnet er das Objekt der äußeren Klasse, in der die +aktuelle Instanz der inneren Klasse erzeugt wurde. In getQualifiedName +würde Outer.this also die +in Zeile 030 erzeugte +Instanz outer bezeichnen. +
+
![]() |
+
+
+ +Die Implementierung von lokalen Klassen konnte im JDK 1.1 ohne größere +Änderungen der virtuellen Maschine vorgenommen werden. Lokale +Klassen sind zwar zum Compilezeitpunkt bekannt, werden aber zur Laufzeit +behandelt wie normale Klassen. Insbesondere wird vom Compiler zu jeder +lokalen Klasse eine eigene .class-Datei +erzeugt. Um Überschneidungen zu vermeiden, besteht ihr Name aus +dem Namen der äußeren Klasse, gefolgt von einem Dollarzeichen +und dem Namen der inneren Klasse. Bei den später behandelten +anonymen Klassen wird statt des Namens der inneren Klasse eine vom +Compiler vergebene fortlaufende Nummer verwendet. Beim Übersetzen +des vorigen Beispiels würden also die Klassendateien Outer.class, +Outer$Inner.class und Listing1001.class +generiert. |
+
+
|
+![]() |
+
+Innere Klassen können nicht nur auf der äußersten +Ebene einer anderen Klasse definiert werden, sondern auch innerhalb +ihrer Methoden und sogar innerhalb eines beliebigen Blocks. In diesem +Fall können sie auch auf die lokalen Variablen der umgebenden +Methode oder des umgebenden Blocks zugreifen. Bedingung ist allerdings, +dass diese mit Hilfe des Schlüsselworts final +als konstant deklariert wurden. + +
+Diese Art, lokale Klassen zu definieren, ist nicht sehr gebräuchlich, +taucht aber mitunter in fremdem Programmcode auf. In der Praxis werden +an ihrer Stelle meist anonyme Klassen verwendet, wie sie im folgenden +Abschnitt besprochen werden. Wir wollen uns dennoch ein einfaches +Beispiel ansehen: + + +
+
+
+
+001 /* Listing1002.java */
+002
+003 class Outer2
+004 {
+005 public void print()
+006 {
+007 final int value = 10;
+008
+009 class Inner2
+010 {
+011 public void print()
+012 {
+013 System.out.println("value = " + value);
+014 }
+015 }
+016
+017 Inner2 inner = new Inner2();
+018 inner.print();
+019 }
+020 }
+021
+022 public class Listing1002
+023 {
+024 public static void main(String[] args)
+025 {
+026 Outer2 outer = new Outer2();
+027 outer.print();
+028 }
+029 }
+
+ |
++Listing1002.java | +
+Die häufigste Anwendung lokaler Klassen innerhalb von Methoden +besteht darin, diese anonym zu definieren. Dabei erhält +die Klasse keinen eigenen Namen, sondern Definition und Instanzierung +erfolgen in einer kombinierten Anweisung. Eine anonyme Klasse ist +also eine Einwegklasse, die nur einmal instanziert werden kann. Anonyme +Klassen werden normalerweise aus anderen Klassen abgeleitet oder erweitern +existierende Interfaces. Ihre wichtigste Anwendung finden sie bei +der Definition von Listenern für grafische Oberflächen, +auf die wir in Kapitel 28 +noch ausführlich eingehen werden. + +
+Als einfaches Anwendungsbeispiel wollen wir das in Listing 9.14 +definierte Interface DoubleMethod +noch einmal verwenden und zeigen, wie man beim Aufruf von printTable +eine anonyme Klasse erzeugt und als Argument weitergibt: + + +
+
+
+
+001 /* Listing1003.java */
+002
+003 public class Listing1003
+004 {
+005 public static void printTable(DoubleMethod meth)
+006 {
+007 System.out.println("Wertetabelle " + meth.toString());
+008 for (double x = 0.0; x <= 5.0; x += 1) {
+009 System.out.println(" " + x + "->" + meth.compute(x));
+010 }
+011 }
+012
+013 public static void main(String[] args)
+014 {
+015 printTable(
+016 new DoubleMethod()
+017 {
+018 public double compute(double value)
+019 {
+020 return Math.sqrt(value);
+021 }
+022 }
+023 );
+024 }
+025 }
+
+ |
++Listing1003.java | +
+Statt eine vordefinierte Klasse zu instanzieren, wird hier in Zeile 016 +eine lokale anonyme Klasse definiert und instanziert. Syntaktisch +unterscheidet sie sich von der Instanzierung einer vordefinierten +Klasse dadurch, dass nach dem new KlassenName(...) +nicht ein Semikolon, sondern eine geschweifte Klammer steht. Anschließend +folgt die Definition der Klasse. Wird als Klassenname ein Interface +angegeben, implementiert die anonyme Klasse dieses Interface. Handelt +es sich dagegen um den Namen einer Klasse, wird die anonyme +Klasse daraus abgeleitet. In unserem Beispiel wird das Interface DoubleMethod +implementiert. +
+
![]() |
+
+
+ +Das Programm hat durch die Verwendung der anonymen Klasse nicht unbedingt +an Übersichtlichkeit gewonnen. Tatsächlich sind sowohl Nutzen +als auch Syntax anonymer Klassen Gegenstand vieler Diskussionen gewesen. +Der große Vorteil anonymer Klassen besteht in ihrer Flexibilität. +Eine anonyme Klasse kann da deklariert werden, wo sie gebraucht wird +(hier beispielsweise beim Aufruf von printTable). +Zudem kann sie Code weitergeben, der auf lokale Variablen und Membervariablen +ihrer unmittelbaren Umgebung zugreift. + + +Ihre Anwendung sollte sich auf die Fälle beschränken, in +denen eine Klasse mit wenigen Zeilen Code erzeugt werden muss, die +nur an einer bestimmten Programmstelle bedeutsam ist. Ist die Klasse +umfangreicher oder wird sie an verschiedenen Stellen benötigt, +sollte eine benannte Klasse definiert und an den Aufrufstellen instanziert +werden. |
+
+
|
+![]() |
+
+Die letzte Variante innerer Klassen, die wir betrachten wollen, ist +eigentlich gar keine. Hierbei wird eine Klasse innerhalb einer anderen +Klasse definiert und mit dem Attribut static +versehen. In diesem Fall erzeugt der Compiler Code, der genau dem +einer gewöhnlichen globalen Klasse entspricht. Insbesondere ist +eine statische lokale Klasse nicht nur innerhalb der Klasse sichtbar, +in der sie definiert wurde, sondern kann auch von außen instanziert +werden. Sie hält auch keinen Verweis auf die instanzierende Klasse +und kann somit nicht auf deren Membervariablen zugreifen. Der einzige +Unterschied zu einer globalen Klasse besteht darin, dass der Name +der inneren Klasse als Präfix den Namen der äußeren +Klasse enthält. Beide sind durch einen Punkt voneinander getrennt. + +
+Eine Klasse könnte beispielsweise dann als statische lokale Klasse +definiert werden, wenn ihre Daseinsberechtigung auf der Existenz der +äußeren Klasse basiert. Typische Anwendungen sind kleinere +Hilfsklassen, die ausreichend Substanz zur Deklaration einer eigenen +Klasse, aber zu wenig für eine eigene Datei haben. Durch den +separaten Namensraum können sie auch Allerweltsnamen wie »Entry«, +»Element« oder »Debug« haben. + +
+Das folgende Listing zeigt eine einfache Anwendung statischer lokaler +Klassen: + + +
+
+
+
+001 /* Listing1004.java */
+002
+003 class Outer3
+004 {
+005 static class Inner3
+006 {
+007 public void print()
+008 {
+009 System.out.println("Inner3");
+010 }
+011 }
+012 }
+013
+014 public class Listing1004
+015 {
+016 public static void main(String[] args)
+017 {
+018 Outer3.Inner3 inner = new Outer3.Inner3();
+019 inner.print();
+020 }
+021 }
+
+ |
++Listing1004.java | +
+
![]() |
+
+
+ +Lokale und anonyme Klassen werden recht häufig eingesetzt. Auch +in diesem Buch sind weitere Beispiele für ihre Anwendung zu finden. +So wird beispielsweise in Abschnitt 28.2.2 +erläutert, wie man sie zur Entwicklung von Ereignishandlern für +GUI-Programme nutzen kann. |
+
+
|
+![]() |
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |