Innere und anonyme Klassen

Innere Klassen

💬 eng.: inner class oder auch nested class

Als innere Klassen werden Klassen bezeichnet, die innerhalb anderer Klassen definiert sind. Dies wird u.a. dazu genutzt, den Coder besser zu strukturieren: Manchmal ist eine Klasse semantisch so sehr einer anderen Klasse untergeordnet und wäre alleine unbrauchbar, dass sie am besten nicht in einer eigenen Datei steht:

class ContactData {
    private String firstName;
    private String lastName;
    private Address address;

    //...

    class Address {
        private String street;
        private String city;

        //...
    }
}

💬 In diesem Beispiel geht es nur um die Semantik der verwendeten Attribute. In der Realität bräuchte man für die Adresse natürlich nicht unbedingt eine eigene Klasse!

In diesem Beispiel wird die Klasse Address eventuell sogar niemals außerhalb der Klasse Customer genutzt. Das wäre dann eigentlich ein Fall für eine private innere Klasse, denn innere Klassen können (anders als normale Klassen) private oder auch protected sein (siehe auch Sichtbarkeitsmodifizierer).

Innere Klassen können aber auch “von außerhalb” genutzt werden:

class Outer {
    private String s = "something";

    class Inner {
        public void foo(){
            System.out.println(s);
        }
    }
}

Von einer anderen Klasse aus ließe sich nun folgendes tun:

public static void main(String[] args) {
	Outer outer = new Outer();
	Outer.Inner inner = outer.new Inner();
	inner.foo();
}

Hier sehen wir gleich mehrere interessante Dinge: 1) Wir benötigen eine Instanz von Outer, um eine Instanz von Inner erzeugen zu können (es sei denn Inner ist static - denn auch das ist möglich!). 2) Von Inner aus kann auf Attribute und Methoden von Outer zugegriffen werden!

Wäre Inner eine statische innere Klasse …

class Outer {
    static class Inner {
        // ...
    }
}

… dann wäre der Zugriff auf Inner auch ohne eine Instanz von Outer möglich (ähnlich wie bei statischen Methoden oder Klassenvariablen):

Outer.Inner inner = new Outer.Inner();

⚠️ Natürlich kann eine statische innere Klasse auch nur auf statische 👉 Member der äußeren Klasse zugreifen!

🔗 Eine hübsche Übersicht zu inneren Klassen (mit praktischen Beispielen und Code zum Ausführen) findet man in diesem W3Schools-Artikel.

Anonyme Klassen

Eine anonyme Klasse trägt keinen Namen und wird in ein und dem selben Statement deklariert und instanziiert. Da in Java jedes Objekt aber eine klare Typ-Zuordnung braucht, muss diese anonyme Klasse entweder eine bestehende Klasse erweitern oder ein Interface implementieren. Die Syntax ist in beiden Fällen gleich, da weder extends noch implements verwendet werden.

Nehmen wir folgende Klasse Foo an:

public class Foo {
	public void saySomething() {
		System.out.println("foo");
	}
}

Indem wir eine anonyme Klasse verwenden, können wir sehr schnell und ohne eine neue .java Datei anzulegen eine “Wegwerf”-Erweiterung von Foo mit überschriebener Methode foo() deklarieren und sofort eine Instanz davon erzeugen:

// Instanz von "Foo" erzeugen
Foo foo = new Foo();

// Instanz von "Bar" (Erweiterung von "Foo") erzeugen
Foo bar = new Foo() {
    @Override
    public void saySomething() {
        System.out.println("bar");
    }
};

foo.saySomething();
bar.saySomething();

Die Ausgabe dieses Codes wäre - wenig überraschend:

foo
bar

Diese anonyme Klasse lässt sich (durch den fehlenden Namen) nur ein einziges mal instanziieren. Es sei denn, die Methode, in der das geschieht, wird mehrmals aufgerufen - aber in diesem Fall ließe sich wohl darüber diskutieren, ob es sich dann nicht eigentlich um einmalige Instanzen unterschiedlicher Klassen mit identischer Deklaration handelt.

⚠️ Wäre Foo in diesem Beispiel ein Interface, sähe der Code zur Deklaration der anonymen Klasse (die Foo implementiert) exakt genauso aus! Anonyme Klassen erweitern oder implementieren Klassen oder Interfaces also implizit - je nach dem, worum es sich im Einzelfall handelt.

Ein sehr häufiger Anwendungsfall für anonyme Klassen in Java sind 🔗 Listener für Buttons (o.ä.) in 👉 GUIs, bei denen zum Zeitpunkt der Initialisierung noch schnell die Methode überschrieben werden soll, die ausgeführt wird, wenn das jeweilige Ereignis (Klick o.ä.) eingetreten ist. Das sieht dann z.B. so aus:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        button.setText("I was clicked!");
    }
});

🔗 Eine gute weiterführende Ressource (in englischer Sprache) ist dieser Artikel.