Javaソースコードの分析とインタビューの質問-難題を打ち破る:Lambdaソースコードの見方

関連するブログ、ムークラス参照列この一連のJavaソースコードとシステムメーカーは簡潔Zhentiインタビュアー
この列の下には、GitHubのアドレスです:
ソースは解決:https://github.com/luanqiu/java8
記事デモ:HTTPS:// GitHubの。 com / luanqiu / java8_demo
クラスメートは必要に応じてそれを見ることができます)

Javaソースコードの分析とインタビューの質問-難題を打ち破る:Lambdaソースコードの見方

入門フレーズは、
我々はすべてのJava8は、ラムダ式を追加して、ラムダ式は、この章ラムダは、例えば、最初のセクションは、その基になる説明に多くのことを行うために数行のコードで、コードの最適化の多くを使用することができることを知っています実行の原則である2番目のセクションでは、作業におけるラムダフローの一般的なポーズについて説明します。

1デモ

:次のように最初に我々は、ラムダデモの表現を見て
ここに画像の説明を挿入
、新しいスレッドが単語を印刷開始され、コードは比較的簡単ですが、するための図で() -> System.out.println ( “ lambda is run “ )、多くの学生は非常に混乱して感じたことを推定し、この種のコードは、Javaは、このような特定する方法でありますコード?

我々は匿名内部クラスの文言を修正した場合は、次のように、誰もが理解できることが明らかになった:
ここに画像の説明を挿入
それはと言っているわけではない() -> System.out.println ( “ lambda is run “ )コードのこのフォームは、実際には、内部クラスを設立しますか?実際、これは最も単純なラムダ式です。ソースコードとその基礎となる構造をIDEAから見ることはできません。基礎となる実装を確認するいくつかの方法を紹介しましょう。

2異常判定方法

私たちは次のように基本的には、多くの場合、隠されたコードを見ることができ、この方法は簡単かつ効率的で一般的な、スタックはその軌道を説明します、スタックをプリントアウトし、コードの実行中に例外をスローするためのイニシアチブをとることができ、我々は、試してみて:
ここに画像の説明を挿入
から例外スタックでは、JVMが現在のクラスの内部クラスを自動的に作成していることがわかります(エラースタックに複数回表示される$は、内部クラスがあることを示します。内部クラスのコードは実行中に例外をスローしますが、ここに表示されますコードが不明なソースであるため、デバッグできません。通常、例外はコード実行のパスを公開する可能性があります。ブレークポイントを解除して再度実行できますが、Lambda式の場合は、例外判定メソッドを使用します。内部クラスがあることは明らかですが、内部クラスのソースコードは表示されません。

3 javapコマンドメソッド

JavapはJavaに付属するツールであり、クラスのバイトコードファイルを表示できます。基本的なJava環境を備えたコンピューターは、次に示すようにjavapコマンドを直接実行できます。
ここに画像の説明を挿入
コマンドオプションでは、主に-v -verboseコマンドを使用して完了しますバイトコードファイルの内容を出力します。

次に、javapコマンドを使用してLambda.classファイルを表示します説明の間に、クラスファイルについての知識を持ち込みます。

コマンドウィンドウでLambda.classの場所を見つけ、コマンドを実行しjavap -verbose Lambda.classます。次に、長いリストが表示されます。これらはアセンブリーインストラクションと呼ばれ、1つずつ説明します(すべての参照はJava仮想マシンからのものです)ノルム、1つずつ引用されなくなった):

アセンブリの説明では、定数プールで始まるタイプの長いリストを簡単に見つけることができます。これを定数プールと呼びます。公式の英語はランタイム定数プールと呼ばれます。これは単に定数でいっぱいの表として理解されています。表には明確な数値とテキスト、クラス、メソッド、フィールドなどの情報を入力します。表の各要素はcpinfoと呼ばれます。cpinfoは一意の識別子(タグ)+名前で構成されます。現在、タグのタイプは合計で
ここに画像の説明を挿入
あり、解析した画像の一部を投稿します。
ここに画像の説明を挿入

  1. 図の定数プールという言葉は、現在の情報が定数プールであることを表しています。
  2. 各行はcp_infoであり、最初の列の#1は定数プールインデックスの1の位置を表します。
  3. 各行の2列目はcp_infoの一意の識別子(タグ)です。たとえば、Methodrefは上の表のCONSTANT_Methodrefに対応します(上の表の値は10のタグに対応します)。つまり、現在の行はメソッドの説明情報です。たとえば、メソッドの名前、入力パラメーターのタイプ、出力パラメーターのタイプなど。具体的な意味は、Java仮想マシンの仕様で照会できます。Methodrefのスクリーンショットは次のとおりです。
    ここに画像の説明を挿入
  4. 各行の3列目は、特定の値の場合、特定の値を直接表示します。複雑な値の場合、cp_infoの参照を表示します。たとえば、図の赤い2は、cp_infoの13と14の2つの位置を示します。 、13はメソッド名がinitであることを示し、14はメソッドに戻り値がないことを示し、結合するとメソッド名と戻り値の型を示すため、パラメーターなしのコンストラクターになります。
  5. 各行の4列目は特定の値です。

cp_infoのより重要なタイプについて、その意味を説明します。

  1. InvokeDynamicは動的呼び出しメソッドを表します。これについては後で詳しく説明します。
  2. Fieldrefは、フィールドの名前やタイプなど、フィールドの説明情報を表します。
  3. NameAndTypeは、フィールドとメソッドタイプの説明です。
  4. MethodHandleメソッドハンドルは、メソッドを動的に呼び出すための一般的な名前です。どのメソッドがコンパイル時に特定されるかはわかりませんが、実行時にどのメソッドが呼び出されるかは確実にわかります。
  5. MethodTypeは動的メソッドタイプです。動的に実行された場合にのみ、そのメソッドタイプがわかります。

私たちは3で赤からプロットされたLjava/lang/invoke/MethodHandles$Lookupjava/lang/invoke/LambdaMetafactory.metafactoryこのコードのように、MethodHandlesLambdaMetafactoryされているjava.lang.invoke私たちは、Java言語は静的にコンパイル時に、言語をコンパイルするために属している知っている、パッケージは主に動的言語の機能を実現しているたinvoke、重要な方法パッケージを、次の、クラス、メソッド、フィールドなどのタイプが決定され、invokeは動的言語を実装します。つまり、クラス、メソッド、フィールドのタイプはコンパイル時ではなく、実行時までしかわかりません。

たとえば、次のコード行:Runnable runnable = () -> System.out.println(“lambda is run”);コンパイラーが()をコンパイルするとき、括弧コンパイラーはそれが何をするかを知りません。実行されたときだけ、これがRunnable.run()メソッドを表していることがわかります。メソッドハンドラー(MethodHandler)と呼ばれるこれらの()を表すために、invokeパッケージ内の多くのクラスが使用されます。コンパイル時に、コンパイラーはこれがメソッドハンドルであることだけを認識し、実際に実行されるメソッドを認識しません。実行中のみです。それまでそれを知らなかったので、質問が来ました。JVMは、実行時に()メソッドのハンドルをどのようにして知り、実際にRunnable.run()メソッドを実行するのですか?

まず、アセンブリ命令の簡単な方法を見てみましょう。
ここに画像の説明を挿入
上の図は、簡単な方法を見ることができるから、() -> System.out.println(“lambda is run”)コードを()、実際にRunnable.run方法です。

上記の図で赤1でマークされている#2定数プールまでさかのぼります。InvokeDynamicは、これが動的呼び出しであることを示しています。2つの定数プールのcp_infoが呼び出され、位置は#0です:#37。著者は// run :()Ljava / lang / Runnableです。これは、JVMが実際に実行されると、Runnable.run()メソッドが動的に呼び出される必要があることを示しています。アセンブリの指示から、()が実際にRunnable.runであることがわかります()、それを証明するためにデバッグしましょう。

上の画像のLambdaMetafactory.metafactoryという単語は3で見つかりました。公式ドキュメントを照会したところ、このメソッドが実行時に実際のコードにリンクするための鍵であることを知り、メタファクトリメソッドにブレークポイントを設定してデバッグしました。次の図に示すように、
ここに画像の説明を挿入
メタファクトリメソッドの入力パラメーターcallerは、動的呼び出しが実際に発生する場所を表し、invokeedNameは呼び出しメソッドの名前を表し、invokeedTypeは呼び出しの複数の入出力パラメーターを表し、samMethodTypeは特定の実装者のパラメーターを表し、implMethodは実際の実装者を表します、InstantiatedMethodTypeはimplMethodと同等です。

上記の内容の要約:
1:アセンブリー命令の単純なメソッドから、Runnable.runメソッドが実行されることがわかります;
2:実際のランタイム中に、JVMは単純なメソッドのinvokedynamic命令に遭遇し、LambdaMetafactory.metafactoryメソッドを動的に呼び出します、特定のRunnable.runメソッドを実行します。

したがって、Lambda式の値の特定の実行は、invokedynamic JVM命令に起因する可能性があります。これは、コンパイラーが何をすべきかわからないにもかかわらず、動的ランタイムで実行される特定のコードを見つけることができるのは、まさにこの命令のためです。

次に、アセンブリ命令の出力の最後を見ると、以下に示すように、例外判定メソッドで見つかった内部クラスが見つかりまし
ここに画像の説明を挿入
た。上図には多くの矢印があり、レイヤーごとの式は現在の内部クラスのすべての情報を明確に示しています。

4まとめ

要約すると、Lambda式の実行は、主にinvokedynamic JVM命令に依存します。ここで示すクラスの完全なパスは、demo.eight.Lambdaです。

公開された40元の記事 ウォンの賞賛1 ビュー5358

おすすめ

転載: blog.csdn.net/aha_jasper/article/details/105609404
おすすめ