Was sind innere Klassen?
Klassen und Interfaces können in Java grundsätzlich ineinander verschachtelt werden. Der Aufbau des Quelltextes sieht dann prinzipiell wie folgt aus:
// Datei: OuterClass.java class OuterClass { //... class InnerClass { //... } //... }
Die äußere Klasse ist hierbei diejenige, die für die
*.java
-Datei namensgebend ist, in diesem Fall OuterClass.java
.
Die Verwendung von inneren Klassen dient nicht der Erweiterung der sprachlichen Möglichkeiten. Sie ist vielmehr geeignet, das Programmdesign zu optimieren, indem ein enger Bezug zwischen eingeschlossener und einschließender Klasse hergestellt wird.
Es existieren vier verschiedenen Arten innerer Klassen. Sie
unterscheiden sich hinsichtlich ihrer Sichtbarkeits-, Zugriffs- und
Verwendungsmöglichkeiten. Durch den Compiler werden sie in
separaten *.class
-Dateien gespeichert. Sie besitzen
Bezeichner, bei denen der Name der äußeren Klasse,
getrennt durch ein Dollarzeichen ($
) von demjenigen der
inneren Klasse gefolgt wird. Einzige Ausnahme ist die Benennung von
anonymen inneren Klassen, die mangels
Bezeichner durchnummeriert werden.
Geschachtelte Top-Level-Klassen
Dieser immer static
deklarierte Typ ist ohne
Objektbildung der umgebenden Klasse sofort in anderen Klassen
verfügbar. Seine Definition kann sich auch in einer anderen
inneren Top-Level-Klasse befinden. Der Schachtelungstiefe sind
hierbei keine Grenzen gesetzt.
Die Bezeichnung dieses Typs zeigt
zudem, dass sich das Verhalten und die Handhabung nur wenig von
'normalen' Top-Level-Klassen unterscheiden. Insbesondere
können Objekte ohne eine Referenz auf die äußere
Klasse erzeugt werden.
package test; public class OuterClass { static String s1 = "Variable static OuterClass.s1 called"; String s2 = "Variable OuterClass.s2 called"; public static class InnerClass { public void print() { System.out.println("InnerClass#print() called"); System.out.println(s1); System.out.println(s2); // Fehler } } }
Wird eine geschachtelte innere Klasse angesprochen, so muss der
Packagename und der Name der umgebenden Klasse dem Bezeichner der
inneren Klasse, verbunden durch den Punkt-Operator, vorangestellt
werden. Eine entsprechende import
-Anweisung ist
natürlich ebenso zulässig.
import test.OuterClass; public class Test { public static void main(String[] args) { OuterClass.InnerClass ai = new OuterClass.InnerClass(); // alternativ unter Wegfall der import-Anweisung // test.OuterClass.InnerClass ai = new test.OuterClass.InnerClass(); ai.print(); } }
Für einen Zugriff auf Variablen der äußeren Klasse
aus der inneren heraus müssen diese static
deklariert sein, da die Ansprache ja aus einer statischen Umgebung
heraus erfolgt.
Möchte man umgekehrt in der
äußeren Klasse auf Variablen der inneren zugreifen, so
gelten die gängigen Regeln: Der Zugriff kann entweder
über ein Objekt der inneren Klasse, oder, bei static
-Deklaration
der Variablen, mit vorangestelltem Klassenbezeichner ohne
Objektbildung stattfinden.
Wie oben bereits erwähnt,
versieht der Compiler die *.class
-Dateien von
geschachtelten Top-Level-Klassen bei der Compilation mit einem
Bezeichner, der aus dem Namen der äußeren Klasse und dem
der inneren Klasse getrennt durch ein '$
' besteht.
Elementklassen
Elementklassen sind 'echte', im Codeblock einer anderen Klasse
definierte und beliebig tief schachtelbare Klassen, die nicht static
deklariert sind und die keine nur static
deklarierten
Elemente enthalten dürfen. Konstanten, als static
und final
deklarierte Variablen, sind jedoch
zulässig.
Elementklassen besitzen Zugriff auf alle, auch private
oder static
deklarierte Elemente der umgebenden
Klasse.
public class OuterClass { private String s1 = "Variable OuterClass.s1 called"; static String s2 = "Variable OuterClass.s2 called"; public class InnerClass { static final String s3 = "static und final"; // zulässig static String s4 = "nur static"; // Fehler public void print() { System.out.println(s1); System.out.println(s2); } } }
Elementklassen existieren nicht unabhängig von ihrer
umgebenden Klasse. Jedes ihrer Objekte ist einem der
äußeren Klasse zugeordnet. Die *.class
-Datei
besteht auch hier aus dem Namen der äußeren Klasse und
demjenigen der inneren getrennt durch ein '$
'.
Bei
der Objektbildung einer Elementklasse ist auf die etwas
ungewöhnliche Schreibweise zu achten, bei der das Objekt der
äußeren Klasse durch den Punktoperator mit dem
Konstruktoraufruf durch new
der inneren Klasse
verbunden wird.
OuterClass oc = new OuterClass(); OuterClass.InnerClass ic = oc.new InnerClass(); // liefert oc$ic.class
Befinden sich in äußerer und innerer Klasse Elemente mit
gleichen Bezeichnern, so werden diejenigen der äußeren
Klasse in der inneren überdeckt. Um in solchen Fällen
dennoch auf die Elemente der äußeren Klasse zugreifen zu
können, verwendet man das Schlüsselwort this
in Kombination mit dem äußeren Klassennamen:
public class OuterClass { private String s1 = "Variable OuterClass.s1 called"; public class InnerClass { private String s1 = "Variable InnerClass.s1 called"; public void print() { System.out.println(OuterClass.this.s1); // Variable OuterClass.s1 called } } }
Lokale Klassen
Unter lokalen Klassen versteht man solche, die in
Anweisungsblöcken von Methoden, Konstruktoren, etc. einer
äußeren Klasse definiert sind. Lokale Interfaces sind
nicht möglich. Auch Zugriffsmodifikatoren sind bei der
Klassendeklaration nicht zulässig. Klassenmethoden und
statische Variablen dürfen in lokalen Klassen nicht vorhanden
sein, static
und final
deklarierte
Konstanten allerdings sehr wohl.
public class OuterClass { String s1 = "Variable OuterClass.s1 called"; public void print() { String s2 = "Variable s2 in OuterClass.print() called"; class InnerClass { String s3 = "Variable InnerClass.s3 called"; InnerClass() { System.out.println(s1); // Variable OuterClass.s1 called System.out.println(s2); // Variable s2 in OuterClass.print() called System.out.println(s3); // Variable InnerClass.s3 called } } new InnerClass(); System.out.println(s3); // Fehler } public static void main(String[] args) { new OuterClass().print(); } }
Lokale innere Klassen haben direkten Zugriff auf die Variablen der äußeren Klasse, umgekehrt jedoch ist ein Zugriff auf die Variablen der inneren Klasse nur mit einem Objekt dieser möglich.
Anonyme innere Klassen
Anonyme Klassen sind innere Klassen, von denen immer ein Objekt
gebildet wird, die jedoch keinen eigenen Bezeichner besitzen. Aus
diesem Grund werden sie vom Compiler auch in durchnummerierten *.class
-Dateien
abgelegt, wobei dem Namen der äußeren Klasse, getrennt
durch ein Dollar-Zeichen, die laufende Nummer der in der Klasse
enthaltenden inneren anonymen Klassen folgt. Dies gilt auch bei
stärkerer Schachtelung. So werden beim u.a. Beispiel vom
Compiler drei *.class
-Dateien erzeugt: AnonClass.class
,
AnonClass$1.class
, und AnonClass$1$1.class
.
Innere anonyme Klassen verhalten sich wie lokale Klassen. Sie
dürfen darüber hinaus weder extends
- noch implements
-Angaben
und keinen Konstruktor enthalten. Sie erweitern entweder eine
bestehende Klasse oder sie erben von Object
und
implementieren ein Interface. Häufig findet man letztere
Variante bei der Ereignis-Behandlung.
import java.awt.BorderLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; public class AnonClass { public AnonClass() { init(); } private void init() { JButton butt = new JButton("klick"); butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(new Rectangle(40, 20) { public String toString(){ return "Masse des Rechtecks " + getWidth() + "x" + getHeight(); }; }); } }); JFrame frame = new JFrame("Anon. Class Beispiel"); frame.add(butt, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(200, 200); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new AnonClass()); } }
Das Beispiel demonstriert beide Varianten. Eine anonyme Klasse wird
der Methode addActionListener()
als Parameter
übergeben. Man erkennt hier, dass nach new
das
Interface ActionListener
angesprochen und im sich
anschließenden Block dessen Methode actionPerformed()
implementiert wird.
Im Codeblock der Methode wird eine
Consolenausgabe mit System.out.println()
erzeugt. Der
Methode println()
wird hierbei wiederum eine anonyme
von Rectangle
abgeleitete Klasse als Parameter
übergeben, in der toString()
überschrieben
wird.
Ausgegeben wird also die String
-Repräsentation
des automatisch erzeugten Rectangle
-Objektes, das
wiederum durch Klick auf den Button und das damit verbundene
Event-Handling ausgeführt wird.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.