System.out.println("lambdaisrun") hat? ist eigentlich die Einrichtung von Was ist mit inneren Klassen? Tatsächlich ist dies der einfachste Lambda-Ausdruck"/> System.out.println("lambdaisrun") hat? ist eigentlich die Einrichtung von Was ist mit inneren Klassen? Tatsächlich ist dies der einfachste Lambda-Ausdruck">
Schauen wir uns zunächst eine Demo des Lambda-Ausdrucks an, wie unten gezeigt:
Der Code ist relativ einfach. Starten Sie einfach einen neuen Thread, um einen Satz zu drucken. aber für das Bild () -> System.out.println („Lambda wird ausgeführt“) ist diese Art von Code wahrscheinlich für viele Studenten sehr verwirrend. Wie erkennt Java diese Art von Code?
Wenn wir die Schreibweise in anonyme innere Klasse ändern, wird es sehr klar und jeder kann es verstehen, wie unten gezeigt:
Bedeutet das, dass () -> System.out.println ( " Lambda ausgeführt wird " ) Diese Codeform erstellt tatsächlich eine interne Klasse? Tatsächlich ist dies der einfachste Lambda-Ausdruck. Wir können den Quellcode und seine zugrunde liegende Struktur nicht über IDEA sehen. Hier stellen wir verschiedene Möglichkeiten vor, um seine zugrunde liegende Implementierung anzuzeigen.
Wir können während der Codeausführung aktiv Ausnahmen auslösen und den Stapel ausdrucken. Im Allgemeinen ist diese Methode einfach und effizient, und Sie können den versteckten Code in vielen Fällen sehen Versuchen wir es wie folgt:
Aus dem Ausnahmestapel können wir ersehen, dass die JVM automatisch eine interne Klasse für die aktuelle Klasse erstellt hat (das mehrfache Erscheinen von $ im Fehlerstapel zeigt an, dass es eine interne gibt). Klasse) und die interne Klasse Während der Ausführung des Codes wurde eine Ausnahme ausgelöst, aber der hier angezeigte Code ist eine unbekannte Quelle, sodass wir ihn unter normalen Umständen nicht debuggen können Haltepunkte setzen und erneut ausführen, aber für Lambda-Ausdrücke wissen wir durch die Ausnahmebeurteilungsmethode nur, dass es eine innere Klasse gibt, aber wir können den Quellcode in der inneren Klasse nicht sehen.
Javap ist ein mit Java geliefertes Tool, das Klassenbytecodedateien anzeigen kann. Computer, auf denen die Java-Basisumgebung installiert ist, können den Javap-Befehl direkt ausführen, wie unten gezeigt:
In der Befehlsoptionen: Wir verwenden hauptsächlich den Befehl -v -verbose, um den Inhalt der Bytecode-Datei vollständig auszugeben.
Als nächstes verwenden wir den Befehl javap, um die Datei Lambda.class anzuzeigen. Während der Erklärung bringen wir einige Kenntnisse über Klassendateien mit.
Wir finden den Speicherort von Lambda.class im Befehlsfenster, führen den Befehl aus: javap -verbose Lambda.class, und dann sehen Sie eine lange Liste von Dingen, diese werden als Montageanweisungen bezeichnet. Lassen Sie uns sie einzeln erklären ( Alle Referenzmaterialien stammen aus der Java Virtual Machine Specification und werden nicht einzeln zitiert:
In den Montageanweisungen können wir leicht eine lange Liste von Typen finden, die mit Constant Pool beginnen. Wir nennen es den Constant Pool Der englische Name lautet Run-Time Constant Pool. Wir verstehen darunter einfach eine Tabelle, die zur Kompilierungszeit klare Zahlen und Texte sowie Typinformationen zu Klassen, Methoden und Feldern enthält. Jedes Element in der Tabelle heißt cpinfo und besteht aus einem eindeutigen Bezeichner (Tag) + Namen. Derzeit gibt es insgesamt folgende Tag-Typen:
Geposteter Teil des von uns analysierten Bildes:cp_info
und #1 in der ersten Spalte stellt die dar Position des Konstantenpool-Index 1; cp_info
,第一列的 #1 代表是在常量池下标为 1 的位置 ;
每行的第二列,是 cp_info
的唯一标识 ( tag ) ,比如 Methodref 对应着上表中的 CONSTANT_Methodref(上上图中表格中 value 对应 10 的 tag),代表当前行是表示方法的描述信息的,比如说方法的名称,入参类型,出参数类型等,具体的含义在 Java 虚拟机规范中都可以查询到,Methodref 的截图如下:
每行的第三列,如果是具体的值的话,直接显示具体的值,如果是复杂的值的话,会显示 cp_info
的引用,比如说图中标红 2 处,引用两个 13 和 14 位置的 cp_info
cp_info
. Methodref entspricht beispielsweise CONSTANT_Methodref in der obigen Tabelle (der Wert in Die obige Tabelle entspricht dem Tag 10), der darstellt. Die aktuelle Zeile stellt die Beschreibungsinformationen der Methode dar, z. B. den Methodennamen, den Eingabeparametertyp, den Ausgabeparametertyp usw. Die spezifische Bedeutung finden Sie in Java Virtual Maschinenspezifikation. Der Screenshot von Methodref lautet wie folgt: cp_info
verwiesen > wird angezeigt. Die rote 2-Markierung im Bild bezieht sich beispielsweise auf zwei cp_info, 13 bedeutet, dass der Methodenname init ist, 14 bedeutet, dass die Methode keine Rückgabe hat Wert und die Kombination aus Methodenname und Rückgabetyp ist ein Parameterloser Konstruktor <p></p>🎜🎜Die vierte Spalte jeder Zeile ist der spezifische Wert. 🎜🎜🎜🎜Für den wichtigeren cp_info-Typ erklären wir seine Bedeutung: 🎜<ol class=" list-paddingleft-2">
<li><p>InvokeDynamic stellt die dynamische Aufrufmethode dar, die wir später ausführlich erläutern werden. </p></li>
<li><p>Fieldref stellt die Beschreibungsinformationen des Feldes dar, z. B. den Namen und den Typ des Feldes der Feld- und Methodentyp; </p></li>MethodHandle-Methodenhandle, der allgemeine Name für dynamische Aufrufmethoden. Wir wissen nicht, welche Methode zur Kompilierungszeit spezifisch ist, aber wir werden definitiv wissen, welche Methode zur Laufzeit aufgerufen wird <li>MethodType ist ein dynamischer Methodentyp. Nur wenn er dynamisch ausgeführt wird, weiß er, um welchen Methodentyp es sich handelt. <p></p>
</li>
<li>Von den drei im obigen Bild rot markierten Stellen haben wir Ljava/lang/invoke/MethodHandles$Lookup, java/lang/invoke/LambdaMetafactory.metafactory gefunden, ähnliche Codes wie dieser, MethodHandles und LambdaMetafactory sind beide Java. lang.invoke Wichtige Methoden unter dem Paket: Das Aufrufpaket implementiert hauptsächlich die Funktionen dynamischer Sprachen. Wir wissen, dass die Java-Sprache eine statische kompilierte Sprache ist. Während der Kompilierung wurden die Typen von Klassen, Methoden, Feldern usw. bestimmt. und invoke implementiert a Es handelt sich um eine dynamische Sprache, was bedeutet, dass die Typen von Klassen, Methoden und Feldern beim Kompilieren nicht bekannt sind, sondern nur beim Ausführen. <p></p>Zum Beispiel diese Codezeile: Runnable runnable = () -> System.out.println(“lambda is run”); Nur wenn es ausgeführt wird. Erst dann werden Sie erkennen, dass dies die Methode Runnable.run() darstellt. Viele Klassen im Invoke-Paket sollen diese () darstellen, die wir Methodenhandles (MethodHandler) nennen. Beim Kompilieren weiß der Compiler nur, dass es sich um ein Methodenhandle handelt, und weiß nicht, welche Methode tatsächlich ausgeführt wird. Ich weiß es bis dahin nicht, daher stellt sich die Frage: Woher weiß die JVM bei der Ausführung, dass das ()-Methodenhandle tatsächlich die Runnable.run()-Methode ausführt? </li>
<li>Schauen wir uns zunächst die Montageanleitung der einfachen Methode an: <p></p>
</li>
</ol>
<p>Auf dem Bild oben können Sie sehen, dass () in der einfachen Methode ausgeführt wird -> ") im Code ( ), ist eigentlich die Runnable.run-Methode. </p>
<p>Wir verfolgen den Konstantenpool Nr. 2, der im obigen Bild mit der roten 1 markiert ist. Der Aufruf ist der cp_info der beiden Konstantenpools Schauen wir uns an, dass # 37 // run:()Ljava/lang/Runnable darstellt, was darauf hinweist, dass die Runnable.run()-Methode dynamisch aufgerufen werden muss. Aus den Assembly-Anweisungen können wir ersehen dass () tatsächlich Runnable .run() ist, lassen Sie uns unten debuggen, um es zu beweisen. </p>
<p>Wir haben die Wörter LambdaMetafactory.metafactory an drei Stellen im obigen Bild gefunden. Durch Abfragen der offiziellen Dokumentation haben wir erfahren, dass diese Methode der Schlüssel zur Verknüpfung mit dem echten Code während der Ausführung ist, also haben wir einen Haltepunkt in der Metafactory-Methode gesetzt debug Wie unten gezeigt: </p>
<p><img src="https://img.php.cn/upload/article/000/465/014/168394740945831.jpg" alt="So lesen Sie Lambda-Quellcode in Java"></p>
<p>metafactory-Methodeneingabeparameteraufrufer stellt den tatsächlichen Speicherort des dynamischen Aufrufs dar, invokedName stellt den Namen der aufrufenden Methode dar, invokedType stellt die mehreren Eingabeparameter und ausgehenden Parameter des Aufrufs dar, samMethodType stellt die Parameter dar des spezifischen Implementierers stellt implMethod den tatsächlichen Implementierer dar, und instanziierterMethodType entspricht implMethod. </p>
<p>Um den obigen Inhalt zusammenzufassen: </p>
<p>1: Aus der einfachen Methode der Assembly-Anweisung können wir ersehen, dass die Runnable.run-Methode ausgeführt wird; </p>
<p>2: Während der tatsächlichen Laufzeit, wenn die JVM auf die invokedynamic-Anweisung trifft Bei der einfachen Methode wird die LambdaMetafactory.metafactory-Methode dynamisch aufgerufen und die spezifische Runnable.run-Methode ausgeführt. <img src="https://img.php.cn/upload/article/000/465/014/168394740935688.jpg" alt="So lesen Sie Lambda-Quellcode in Java"></p>So kann die spezifische Ausführung des Lambda-Ausdruckswerts der invokedynamic JVM-Anweisung zugeschrieben werden. Genau aufgrund dieser Anweisung wissen Sie zwar nicht, was beim Kompilieren zu tun ist, können aber den spezifischen Code finden, der während des Kompilierens ausgeführt werden soll dynamische Laufzeit. <p></p>Dann werfen wir einen Blick auf das Ende der Assembler-Anweisungsausgabe. Wir haben die in der Ausnahmebeurteilungsmethode gefundene interne Klasse gefunden, wie unten gezeigt: <p></p>
<p></p>
<p>Im Bild oben sind viele Pfeile und die aktuelle interne Klasse zu sehen Klasse wird Schicht für Schicht alle Informationen klar ausgedrückt. </p>
Das obige ist der detaillierte Inhalt vonSo lesen Sie Lambda-Quellcode in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!