In Java 1.0:
Ziel: Definition von Hilfsklassen möglichst nahe an der Stelle, wo sie gebraucht werden.
Geschachtelte und innere Klassen sind wesentlich motiviert durch das neue Ereignismodell im AWT von Java1.1.
Klassen oder Interfaces, die static deklariert sind und in andere ``top-level''-Klassen oder Interfaces eingeschachtelt sind.
Interfaces sind immer static, d.h. für Interfaces ist nur die ``top-level''-Variante möglich.
Geschachtelte ``top-level'' Klassen und Interfaces verhalten sich wie andere Paket-Elemente auf der äußersten Ebene, außer daß ihrem Namen der Name der umgebenden Klasse vorangestellt wird.
public class LinkedList { // interface verkettungselement public static interface Linkable { public Linkable getNext(); public void setNext(Linkable n); } // kopf der liste: Linkable head; // einfügen: public void insert(Linkable n) { n.setNext(head); head = n; } // kopfzugriff: public Linkable getHead() { return head; } }
Benutzung:
a) Definition der Verkettungselemente:
class LinkableInteger implements LinkedList.Linkable { int i; LinkedList.Linkable Next; // konstruktor: LinkableInteger(int i) { this.i = i; } // versprochene Methoden: public LinkedList.Linkable getNext() { return Next; } public void setNext(LinkedList.Linkable n) { Next = n; } // für die ausgabe: String-Umwandlung // überschreibe toString aus Object public String toString() { return i + ""; } }
b) Testlauf:
public class LiMain { public static void main (String[] argv) { LinkedList li = new LinkedList(); li.insert(new LinkableInteger(16)); li.insert(new LinkableInteger(6)); li.insert(new LinkableInteger(-4)); LinkedList.Linkable l; for (l = li.getHead(); l != null; l = l.getNext()) System.out.println(l); } }
c) Ergebnis:
-4 6 16
Im Gegensatz zu den static-deklarierten geschachtelten ``top-level''-Klassen, die mehr oder weniger nur zur Strukturierung dienen, sind Elementklassen echte innere Klassen.
Damit kann das Objekt der Elementklasse implizit auf die Instanzvariablen der umgebenden Klasse zugreifen (auch auf private).
Die umgebende Klasse hat Zugriffrechte auf alle Felder aller ihrer Elementklassen. Zwei Elementklassen der selben umgebenden Klasse haben Zugriffrechte auf die Elemente des jeweilig anderen.
Elementklassen dürfen keine statischen Elemente (Attribute, Methoden,
Klassen, Interfaces) besitzen. Gilt auch für lokale Klassen
(Seite )
und anonyme Klassen (Seite
).
Beispielprojekt: Einbau eines Iterators in eine Buchstabenmenge
Beschreibung der Buchstabenmenge:
Ziel: ein Iterator für charset.
Typisches Verwendungsmuster für einen solchen Iterator:
charset cs = new charset(somestring); Enumeration e = cs.characters(); while (e.hasMoreElements()) machwasmit(e.nextElement());
(Anderes Iteratorbeispiel auf Seite ).
a) Originalversion der Buchstabenmenge:
import java.util.BitSet; class charset { // Buchstabenmenge: private BitSet chars = new BitSet(); // Konstruktor: public charset(String str) { for (int i = 0; i < str.length(); i++) chars.set(str.charAt(i)); } // Umwandlung in String: public String toString() { String result="["; int size = chars.size(); for (int i = 0; i < size; i++) if (chars.get(i)) result += (char)i; return result +"]"; } }
b) Testen der Originalversion der Buchstabenmenge:
public class charsetMain { // hauptprogramm public static void main(String argv[]) { // erstes kommandozeilenargument charset cs = new charset(argv[0]); System.out.println(cs + ""); } }
c) Testlauf:
Eingabe:
java charsetMain JavaProgrammierung
Ausgabe:
[JPaegimnoruv]
c) Einbau einer inneren Iteratorklasse
import java.util.* // für BitSet, Enumeration // und NoSuchElementException class charset { // Buchstabenmenge: private BitSet chars = new BitSet(); // Konstruktor: public charset(String str) { for (int i = 0; i < str.length(); i++) chars.set(str.charAt(i)); } // innere Elementklasse: private class cs_iterator implements Enumeration { private int pos; private int setsize = chars.size(); // versprochenes "hasMoreElements": public boolean hasMoreElements() { while (pos < setsize && !chars.get(pos)) pos++; return pos < setsize; }
// versprochenes "nextElement": public Object nextElement() throws NoSuchElementException { if (hasMoreElements()) return new Character((char) pos++); else throw new NoSuchElementException(); } } // liefert einen Iterator: public Enumeration characters() { return new cs_iterator(); } }
d) Testen des neuen charset mit Iterator:
import java.util.*; public class it_charsetMain { public static void main(String argv[]) { // erstes kommandozeilenargument charset cs = new charset(argv[0]); Enumeration e = cs.characters(); while (e.hasMoreElements()) System.out.println(e.nextElement()); } }e) Testlauf:
Eingabe:
java it_charsetMain Java
Ausgabe:
J a v
Syntax-Erweiterungen und neue Regeln für Elementklassen
a) Wie sprechen ``innere Objekte'' das ihnen zugeordnete Objekt der umgebenden Klasse an?
Im charset-Beispiel:
pos und setsize sind Attribute der Selbstreferenz this .
Ihr expliziter Name wäre also this.pos, bzw. this.setsize.
Um chars explizit anzusprechen, schreiben wir:
charset.this.chars
b) Tiefere Schachtelung von Elementklassen?
Beliebig möglich. Allerdings darf keine der eingeschachtelten Klassen so heißen wie eine der sie umgebenden!
Das bedeutet:
Obige neue Syntax:
classname.this
public class A { public string name = "a"; public class B { public string name = "b"; public class C { public string name = "c"; public void print() { System.out.println(name); System.out.println(this.name); System.out.println(C.this.name); System.out.println(B.this.name); System.out.println(A.this.name); } } } }
c) Welches ist das umgebende Objekt?
In unserem Beispiel haben wir den Default-Fall benutzt:
public Enumeration characters() { return new cs_iterator(); }
Hier: Keine Angabe des umgebenden Objektes für die neue Instanz der inneren Klasse. Daher wird die Selbstreferenz this als umgebendes Objekt angenommen:
Äquivalent zu obigem ist die neue explizite Schreibweise:
public Enumeration characters() { return this.new cs_iterator(); }
Für andere Fälle brauchen wir die neue allgemeine Syntax:
containing_instance.new inner_class(...)
wobei containing_instance eine Instanz der umfassenden Klasse von inner_class ist.
d) Innere Objekte außerhalb der umgebenden Klasse erzeugen?
Geht, wenn die innere Klasse nicht wie in unserem charset-Beispiel privat ist.
Also ändern:
// innere Elementklasse: public class cs_iterator implements Enumeration { private int pos; .... }
Dann funktioniert auch folgendes Hauptprogramm:
public static void main(String argv[]) { // erstes kommandozeilenargument charset cs = new charset(argv[0]); Enumeration e = cs.new cs_iterator(); while (e.hasMoreElements()) System.out.println(e.nextElement()); }
Noch ein Beispiel für die externe Erzeugung innerer Objekte.
Klassenhierarchie von Seite :
A a = new A; // A Instanz A.B b = a.new B(); // B Instanz in a A.B.C c = b.new C(); // C Instanz in b c.print(); // Methode aus C
e) Kann man von inneren Klassen erben?
Ja (``strange as it may seem'').
Damit gibt es aber auch eine zweite Art, wie Objekte innerer Klassen erzeugt werden können: mit dem Oberklassen-Konstruktoraufruf super.
Zusätzlich zur Syntax
containing_instance.new inner_class(...)
containing_instance.super(...)
f) Ändern sich durch innere Klassen die Regeln für die Namensbindung?
Ja. Wir haben jetzt drei Hierarchien in Java-Programmen:
Aufgabe der Namensanalyse: Finde zu jedem Auftreten eines Bezeichners die laut Sprachdefinition dazugehörige Definition.
Eine Methode der Klasse K verwendet in einer Anweisung den Bezeicher x.
Ist x
Vererbungshierarchie und Klassenschachtelung im Bild:
Die beiden Hierarchien sind völlig unabhängig voneinander.
Es ist wichtig, sie nicht zu verwechseln!
Konfliktfall zwischen umfassender Klasse und Oberklasse
In Java 1.1. gelöst zugunsten der Oberklasse, d.h.
der Bezeichner x der Oberklasse verdeckt den der umfassenden Klasse.
Zusätzlich zu dieser Regel erfordert Java im Konfliktfall die explizite Benennung des Bezeichners, d.h.
Lokale Klassen sind innere Klassen, die nicht auf oberer Ebene in andere Klassen eingeschachtelt sind, sondern lokal in Anweisungsblöcken von Methoden, statischen Initialisierern oder Instanz-Initialisierern.
Sehr ähnlich zu Elementklassen (siehe Seite )
mit folgenden wichtigen Unterschieden:
Anonyme Klassen werden wie lokale Klassen
(siehe Seite ) z.B. innerhalb von
Anweisungsblöcken definiert.
Anonyme Klassen haben keinen Namen. Sie entstehen immer zusammen mit einem Objekt.
import java.io.*; // gibt die Namen aller .java-Dateien im // angegebenen Verzeichnis aus public class dirlist {public static void main(String argv[]) { File f = new File(argv[0]); String[] list = f.list (new FilenameFilter() { public boolean accept(File f, String s) { return s.endsWith(".java"); } } ); // Formatierung schwierig! for (int i = 0; i < list.length; i++) System.out.println(list[i]); } }
Die Syntax für anonyme Klassen ist
new-expression class-body
Für die anonyme Klasse kann man keine extends- oder implements-Klauseln angeben.
Daher:
In unserem Beispiel implementiert die anonyme Klasse ein Interface:
f.list(new FilenameFilter() { ... } ):
Da anonyme Klassen keinen Namen haben, können sie auch keine
Konstruktoren
haben. Deshalb hat Java 1.1
die
Instanzinitialisierungsblöcke eingeführt
(Seite ).