Freunde, wie ihr bereits bemerkt habt, werden wir Ende Juni viele neue Gruppen gründen, darunter den nächsten Stream des Kurses
"Java Developer" , den alle mochten. Im Moment teilen wir Ihnen eine neue Übersetzung mit, die für Studenten dieses Kurses vorbereitet wurde.

Beim Hinzufügen neuer Funktionen zur Programmiersprache achten Sprachentwickler normalerweise auf Konflikte mit den aktuellen Funktionen der Sprache, auf Änderungen, die zu Inkompatibilität mit früheren Versionen führen, auf Fehler und auf Situationen, die zu undefiniertem oder unerwartetem Verhalten führen können.
Oft achten sie jedoch nicht genug auf geringfügige Änderungen an neuen, praktischen Techniken zum Schreiben von Code. Diese Änderungen sind häufig Nebenwirkungen neuer Funktionen. Änderungen dieser Art sind streng genommen keine neuen Funktionen. Dies sind subtile Änderungen, die aufgrund anderer Funktionen oder ihrer Kombinationen aufgetreten sind.
Anonyme innere Klassen
In Java sind innere Klassen Klassen, die als Mitglieder einer Klasse definiert sind. Es gibt vier Arten von inneren Klassen:
- statisch verschachtelt
- intern
- lokal (Methode lokal)
- anonym
Anonyme innere Klassen sind unbenannte Klassen, die eine Implementierung einer vorhandenen Klasse bereitstellen. Solche Klassen werden häufig verwendet, um Ereignisse in der ereignisorientierten Programmierung zu behandeln. Normalerweise bietet eine anonyme innere Klasse eine direkte Implementierung einer abstrakten Klasse. Dies ist jedoch nicht erforderlich. Eine anonyme innere Klasse kann aus einer nicht abstrakten Klasse erstellt werden.
Ich glaube, dass es einen Punkt gibt, der in Bezug auf anonyme innere Klassen nicht vollständig verstanden wird. Tatsache ist, dass der Programmierer tatsächlich
eine Unterklasse der Quellklasse erstellt . Diese Unterklasse erhält den Namen
Class$X
, wobei
Class
die äußere Klasse ist und
X
die Zahl ist, die die Reihenfolge darstellt, in der die inneren Klassen in der äußeren Klasse instanziiert werden. Zum Beispiel ist
AnonDemo$3
die dritte innere Klasse, die in
AnonDemo
. Sie können diese Klassen nicht wie gewohnt aufrufen. Und im Gegensatz zu anderen Arten innerer Klassen ist eine anonyme innere Klasse immer implizit ein Kind des Typs, aus dem sie erstellt wurde (mit Ausnahme der Verwendung von
var
, auf die wir in Kürze eingehen werden).
Schauen wir uns ein Beispiel an.
class Anon { }; public class AnonDemo { public static void main (String[] args) { Anon anonInner = new Anon () { public String toString() { return "Overriden"; }; public void doSomething() { System.out.println("Blah"); }; }; System.out.println(anonInner.toString()); anonInner.doSomething();
In diesem Beispiel haben wir eine anonyme innere Klasse basierend auf der Anon-Klasse instanziiert. Im Wesentlichen haben wir
eine unbenannte Unterklasse einer bestimmten Klasse erstellt . Vor Java 10 waren anonyme innere Klassen fast immer implizit polymorph. Ich sage "fast", weil solch ein nicht polymorpher Code wie dieser natürlich ausgeführt wird.
new Anon() { public void foo() { System.out.println("Woah"); } }.foo();
Wenn wir jedoch das Ergebnis der Erstellung einer Instanz einer anonymen inneren Klasse dem ursprünglichen Typ zuordnen möchten, ist eine solche Operation polymorpher Natur. Die Gründe dafür liegen in der Tatsache, dass wir implizit eine Unterklasse der Klasse erstellen, die wir als Quelle für die anonyme innere Klasse angegeben haben, und nicht auf den bestimmten Objekttyp (
Class$X
) zugreifen können, um ihn im Quellcode anzugeben.
Polymorphismus und anonyme innere Klassen, praktische Konsequenzen
Haben Sie den obigen Code bemerkt? Da wir eine Basisklassenreferenz auf ein Unterklassenobjekt verwenden, können wir gemäß den Gesetzen des Polymorphismus nur auf 1) von der Basisklasse definierte Methoden oder 2) überschriebene virtuelle Methoden in der Unterklasse verweisen.
Daher würde der Aufruf von
toString()
für ein anonymes Objekt der inneren Klasse im vorherigen
toString()
einen überschriebenen Wert für "Overridden" ergeben. Der Aufruf von
doSomething()
würde jedoch zu einem Kompilierungsfehler führen. Was ist der Grund?
Ein Unterklassenobjekt mit einem Verweis auf den Typ der Basisklasse hat über diesen Verweis auf die Basisklasse keinen Zugriff auf die Mitglieder der Unterklasse. Die einzige Ausnahme von dieser Regel besteht darin, dass eine Unterklasse die Basisklassenmethode überschreibt. In diesem Fall verwendet Java, entsprechend seiner polymorphen Natur, den dynamischen Methodenversand, um zur Laufzeit die Version der virtuellen Unterklassenmethode auszuwählen.
Wenn Sie es noch nicht wussten, ist eine
virtuelle Methode eine Methode, die überschrieben werden kann. In Java sind alle nicht endgültigen, nicht privaten und nicht statischen Methoden standardmäßig virtuell. Ich spreche standardmäßig und nicht implizit, da verschiedene JVM Optimierungen durchführen können, die dies ändern können.
Was hat Java 10 damit zu tun?
Ein kleines Feature namens Typinferenz. Schauen Sie sich das folgende Beispiel an:
class Anon { }; public class AnonDemo { public static void main(String[] args) { var anonInner = new Anon() { public void hello() { System.out.println( "New method here, and you can easily access me in Java 10!\n" + "The class is: " + this.getClass() ); }; }; anonInner.hello();
Es funktioniert, wir können
hello()
! Der Teufel steckt im Detail. Wenn Sie mit
var
vertraut sind, verstehen Sie bereits, was hier passiert. Unter Verwendung des reservierten Namens vom Typ
var
konnte
Java den genauen Typ der anonymen inneren Klasse bestimmen. Daher sind wir nicht mehr darauf beschränkt, auf die Basisklasse zu verweisen, um auf das Unterklassenobjekt zuzugreifen.Was haben wir vor Java 10 gemacht, als wir einen Verweis auf eine Unterklasse brauchten?
Es ist kein Geheimnis, dass in der fernen, aber nicht vergessenen Vergangenheit große Debatten über die inneren Klassen geführt wurden. Und wenn dies ein Geheimnis ist, dann ist dies eines der schlimmsten Geheimnisse der Welt. Zweifellos werden viele die Tatsache nicht gutheißen, dass Sie einen genauen Verweis auf eine anonyme innere Klasse benötigen, da die Idee darin besteht, das Hinzufügen von Müll zur Klasse zu vermeiden. Anonyme Klassen sollten verwendet werden, um einen Vertrag im laufenden Betrieb auszuführen, für eine Operation, die logisch mit einer anderen Klasse verknüpft ist, z. B. zum Behandeln von Ereignissen. Wahrscheinlich wurde die neugierige Barbara jedoch nicht von der Nase gerissen, und ich wette, dass die meisten Entwickler sehr neugierig sind. Vielleicht zum Nachteil des gesunden Menschenverstandes!
Vor Java 10 können wir mithilfe der Reflexion einen ähnlichen Effekt erzielen:
Anon anonInner2 = new Anon() { public void hello() { System.out.println("Woah! "); }; }; anonInner2.getClass().getMethod("hello").invoke(anonInner2);
Vollständige Quelle
Kann
hier nehmen
public class VarAnonInner { public static void main (String[] args) throws Exception { var anonInner = new Anon() { public void hello() { System.out.println("New method here, and you can easily access me in Java 10!\n" + "The class is: " + this.getClass() ); }; }; anonInner.hello(); Anon anonInner2 = new Anon() { public void hello() { System.out.println("Woah! "); }; }; anonInner2.getClass().getMethod("hello").invoke(anonInner2); new Anon() { public void hello() { System.out.println("Woah!!! "); }; }.hello();
Wir warten auf Ihre Kommentare und erinnern daran, dass heute um 20.00 Uhr ein
kostenloses Webinar zum Kurs stattfindet.