ホームページ  >  記事  >  Java  >  Java メソッド呼び出しを使用して静的および動的ディスパッチを解決する

Java メソッド呼び出しを使用して静的および動的ディスパッチを解決する

WBOY
WBOY転載
2023-04-20 23:22:211263ブラウズ

メソッド呼び出し

プログラムの実行中、メソッド呼び出しは最も一般的で頻繁な操作です

メソッド呼び出しはメソッドの実行と同じではありません:

  • メソッド呼び出しフェーズの唯一のタスクは、呼び出されるメソッドのバージョン、つまりどのメソッドが呼び出されるかを決定することです。

  • 特定のメソッドは関与しません。メソッド内でプロセスを実行しています

クラス ファイルのコンパイル プロセスには、従来のコンパイルの接続ステップは含まれていません

クラス ファイル内のすべてのメソッド呼び出しは、これは、クラス ファイルに保存されているシンボリック参照であり、実際に実行中のメモリ レイアウト内のメソッドのエントリ アドレス、つまり以前の直接参照ではありません。

  • これにより、次のようになります。 Java には、より強力な動的拡張機能があります

  • 同時に、Java メソッド呼び出しプロセスが比較的複雑になります。

  • これは必要です。クラスのロード時または実行時でもターゲット メソッドの直接参照を確認する

メソッド分析

すべてのメソッド呼び出しのターゲット メソッドは定数への参照ですクラス ファイル内のプール

クラスの読み込みと分析フェーズで、シンボル参照の一部を直接参照に変換します。

メソッドには、プログラムが実行される前に決定可能な呼び出しバージョンがあります。実際に実行され、このメソッドの呼び出しバージョンは実行時に変更できません

つまり、呼び出しターゲットはプログラム コード内で完了しており、コンパイラのコンパイル時に決定する必要があります。これはメソッド分析とも呼ばれます

Java メソッドの分類

Java では、「コンパイル時」に準拠します。「実行時不変」メソッドには 2 つの主要なカテゴリがあることがわかります。

  • #静的メソッド:

    は型に直接関連しています

  • プライベート メソッド:

    外部からアクセスできません

  • これら 2 つのメソッドの特性により、どちらのメソッドも継承や他のメソッドのバージョンによってオーバーライドできないことが決定されるため、クラスのロード段階での解析に適しています。

非-仮想メソッド:

クラスのロード段階で、シンボル参照はメソッドへの直接参照として解析されます。

    静的メソッド
  • プライベート メソッド
  • ##インスタンス コンストラクター
  • ##親クラス メソッド

  • ##仮想メソッド:

    クラスのロード段階では、シンボル参照はメソッドへの直接参照として解決されません

  • 上記の非仮想メソッドを除き、その他のメソッドはすべて仮想メソッドです

staticdispatch

public class StaticDispatch {
	static abstract class Human {
	}
	static class Man extends Human {
	}
	static class Woman extends Human {
	}
	public static void sayHello(Human guy) {
		System.out.println("Hello, Guy!");
	}
	public static void sayHello(Man guy) {
		System.out.println("Hello, Gentleman!");
	}
	public static void sayHello(woman guy) {
		System.out.println("Hello, Lady!");
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human women = new Woman();
		sayHello(man);
		sayHello(woman);
	}
}

Human man = new Human();

Humanは変数の静的型です

Man

は変数の実際の型です。

静的型と実際の型の両方がプログラム内で変更されます。

静的型:

静的型の変更は、

    変数を使用する場合にのみ発生します。静的型自体は変更されません。変更されました
  • #最終的な静的型はコンパイラで認識されます
  • #実際の型:
  • 実際の型変更の結果は実行時に決定されます

#コンパイラは、コンパイル中にオブジェクトの実際の型が何であるかを知りません

    Human human = new Man();
    sayHello(man);
    sayHello((Man)man);		// 类型转换,静态类型变化,转型后的静态类型一定是Man
    man = new woman();		// 实际类型变化,实际类型是不确定的
    sayHello(man);
    sayHello((Woman)man);	// 类型转换,静态类型变化
  • オーバーロードする場合、コンパイラは実際の型ではなくパラメータの静的型を判断基準として使用します。静的型はコンパイル中に確認できます:

    コンパイル フェーズ、Javac コンパイラパラメータの静的型に基づいて、どのオーバーロードされたバージョンを使用するかを決定します。
  • 静的ディスパッチ:

静的型に依存するすべてのメソッドメソッドの検索 ディスパッチ アクションの実行バージョン

典型的なアプリケーション: メソッドのオーバーロード

  • 静的ディスパッチは、コンパイルフェーズ , したがって、静的ディスパッチを決定するアクションは仮想マシンによって実行されず、コンパイラによって完了します.
  • リテラルは静的型を表示しないため、それを理解することしかできません。

    public class LiteralTest {
    	public static void sayHello(char arg) {
    		System.out.println("Hello, char!");
    	}
    	public static void sayHello(int arg) {
    		System.out.println("Hello, int!");
    	}
    	public static void sayHello(long arg) {
    		System.out.println("Hello, long!");
    	}
    	public static void sayHello(Character arg) {
    		System.out.println("Hello, Character!");
    	}
    	public static void main(String[] arg) {
    		sayHello('a');
    	}
    }

    コンパイラは、異なる出力を取得するために、オーバーロードされたメソッドに上から下に注釈を付けます
コンパイラが変換をカスタマイズする型を決定できない場合、型のあいまいさをプロンプトし、コンパイルを拒否します

public class LiteralTest {
	public static void sayHello(String arg) {	// 新增重载方法
		System.out.println("Hello, String!");
	}
	public static void sayHello(char arg) {	
		System.out.println("Hello, char!");
	}
	public static void sayHello(int arg) {
		System.out.println("Hello, int!");
	}
	public static void sayHello(long arg) {
		System.out.println("Hello, long!");
	}
	public static void sayHello(Character arg) {
		System.out.println("Hello, Character!");
	}
	public static void main(String[] args) {
		Random r = new Random();
		String s = "abc";
		int i = 0;
		sayHello(r.nextInt() % 2 != 0 ? s : 1 );	// 编译错误
		sayHello(r.nextInt() % 2 != 0 ? 'a' : false);	//编译错误
	}
}
動的ディスパッチ
public class DynamicDispatch {
	static abstract class Human {
		protected abstract void sayHello();
	}
	static class Man extends Human {
		@override
		protected void sayHello() {
			System.out.println("Man Say Hello!");
		}
	}
	static class Woman extends Human {
		@override
		protected void sayHello() {
			System.out.println("Woman Say Hello!");
		}
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human women = new Woman();
		man.sayHello();
		woman.sayHello();
		man = new Woman();
		man.sayHello();
	}
}

これは静的型に基づいて決定されません

静的型

Human

2 つの変数

man

women

は、
    sayHello()
  • メソッドを呼び出すときに異なる動作を実行します。

    変数man2 つの呼び出しで異なるメソッドが実行されました#この現象の理由

    : 2 つの変数の実際の型は異なります
  • #Java 仮想マシンが実際の型に従ってメソッドの実行バージョンをディスパッチする方法: invokevirtual

    命令
  • のポリモーフィック検索プロセスから始まる、ランタイム解析プロセスinvokevirtual
命令の実行は、大まかに次のステップに分かれています。

記録されたオペランド スタックの先頭の最初の要素が指すオブジェクトの実際の型を見つけます。

C として

  • 型 C で定数内の記述子と単純名に一致するメソッドが見つかった場合、アクセス許可の検証が行われ、検証に合格した場合は、そのメソッドへの直接参照が返され、検索プロセスが終了します; 検証が失敗した場合、java.lang.illegalAccessErrorException

  • 見つからない場合は、各タイプ C 要素が下から上にチェックされます。親クラスは、検索および検証プロセスの 2 番目のステップを実行します。

  • 適切なメソッドが見つからない場合は、java.lang.AbstractMethodError がスローされます。例外

  • Java 言語メソッドのオーバーライドの本質:

    invokevirtual命令実行の最初のステップは、実行時のレシーバの実際のステータス タイプであるため、2 つの呼び出しの invokevirtual 命令は、定数プール内のクラス メソッド シンボル参照を別の直接参照に解決します。実行時の実際の型に基づくメソッドの実行バージョン。これは動的ディスパッチと呼ばれます。

    仮想マシンの動的ディスパッチの実装

    仮想マシンの概念分析のモードには、静的ディスパッチと動的ディスパッチがあります。仮想マシンが

    「何をするか」

    この質問

    仮想マシン「具体的にどうするか」

    に違いがあることがわかります。さまざまな仮想マシンの実装:

      動的ディスパッチは非常に頻繁なアクションであり、動的ディスパッチのメソッド バージョン選択プロセスでは、メソッド メタデータ内で適切なターゲット メソッドを検索する必要があるためです。実行時のクラス
    • したがって、仮想マシンの実際の実装では、パフォーマンス上の理由から、ほとんどの実装ではそのような頻繁な検索は実際には実行されません
    • 最も一般的に使用される「安定性の最適化」 この方法は、メソッド領域にクラスの
    • 仮想メソッド テーブル (vtable) を作成し、
    • メタデータ検索の代わりに

      仮想メソッド テーブル インデックスを使用します。 パフォーマンスを向上させるための

    • 仮想メソッド テーブルには、各メソッドの実際のエントリ アドレスが格納されます。サブクラスでメソッドが呼び出されない 書き換えると、サブクラスの仮想メソッド テーブルのアドレス エントリは親クラスの同じメソッドのアドレス エントリと一致し、両方とも親クラスの実際のエントリを指します

    #サブクラスに重複がある場合 このメソッドを記述した後、サブクラス メソッド テーブル内のアドレスは、サブクラスの実際のメソッドを指すエントリ アドレスに置き換えられます

    • 親クラス、サブクラスで同じシグネチャを持つメソッド クラスの仮想メソッド テーブルには同じインデックス番号があります:

    • このように、タイプが変更されると、検索するメソッド テーブルのみを変更する必要があり、必要なインデックスはインデックスに従って異なる仮想メソッド テーブルから変換できます。エントリ アドレス

    • メソッド テーブルは通常初期化されます。クラス読み込みフェーズの接続フェーズ:

    クラスの変数の初期値を準備した後、仮想マシンも初期化されます

    以上がJava メソッド呼び出しを使用して静的および動的ディスパッチを解決するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。