プログラムの実行中、メソッド呼び出しは最も一般的で頻繁な操作です
メソッド呼び出しはメソッドの実行と同じではありません:
メソッド呼び出しフェーズの唯一のタスクは、呼び出されるメソッドのバージョン、つまりどのメソッドが呼び出されるかを決定することです。
特定のメソッドは関与しません。メソッド内でプロセスを実行しています
クラス ファイルのコンパイル プロセスには、従来のコンパイルの接続ステップは含まれていません
クラス ファイル内のすべてのメソッド呼び出しは、これは、クラス ファイルに保存されているシンボリック参照であり、実際に実行中のメモリ レイアウト内のメソッドのエントリ アドレス、つまり以前の直接参照ではありません。
これにより、次のようになります。 Java には、より強力な動的拡張機能があります
同時に、Java メソッド呼び出しプロセスが比較的複雑になります。
これは必要です。クラスのロード時または実行時でもターゲット メソッドの直接参照を確認する
すべてのメソッド呼び出しのターゲット メソッドは定数への参照ですクラス ファイル内のプール
クラスの読み込みと分析フェーズで、シンボル参照の一部を直接参照に変換します。
メソッドには、プログラムが実行される前に決定可能な呼び出しバージョンがあります。実際に実行され、このメソッドの呼び出しバージョンは実行時に変更できません
つまり、呼び出しターゲットはプログラム コード内で完了しており、コンパイラのコンパイル時に決定する必要があります。これはメソッド分析とも呼ばれます
Java では、「コンパイル時」に準拠します。「実行時不変」メソッドには 2 つの主要なカテゴリがあることがわかります。
は型に直接関連しています
外部からアクセスできません
クラスのロード段階で、シンボル参照はメソッドへの直接参照として解析されます。
##親クラス メソッド
クラスのロード段階では、シンボル参照はメソッドへの直接参照として解決されません
staticdispatchpublic 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();
ManHumanは変数の静的型です
は変数の実際の型です。
静的型の変更は、
#コンパイラは、コンパイル中にオブジェクトの実際の型が何であるかを知りません
Human human = new Man(); sayHello(man); sayHello((Man)man); // 类型转换,静态类型变化,转型后的静态类型一定是Man man = new woman(); // 实际类型变化,实际类型是不确定的 sayHello(man); sayHello((Woman)man); // 类型转换,静态类型变化
静的ディスパッチ:
典型的なアプリケーション: メソッドのオーバーロード
リテラルは静的型を表示しないため、それを理解することしかできません。
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 つの変数women
は、変数man2 つの呼び出しで異なるメソッドが実行されました#この現象の理由
: 2 つの変数の実際の型は異なります#Java 仮想マシンが実際の型に従ってメソッドの実行バージョンをディスパッチする方法: invokevirtual
命令記録されたオペランド スタックの先頭の最初の要素が指すオブジェクトの実際の型を見つけます。
C として
型 C で定数内の記述子と単純名に一致するメソッドが見つかった場合、アクセス許可の検証が行われ、検証に合格した場合は、そのメソッドへの直接参照が返され、検索プロセスが終了します; 検証が失敗した場合、java.lang.illegalAccessErrorException
見つからない場合は、各タイプ C 要素が下から上にチェックされます。親クラスは、検索および検証プロセスの 2 番目のステップを実行します。
適切なメソッドが見つからない場合は、java.lang.AbstractMethodError がスローされます。例外
invokevirtual命令実行の最初のステップは、実行時のレシーバの実際のステータス タイプであるため、2 つの呼び出しの invokevirtual 命令は、定数プール内のクラス メソッド シンボル参照を別の直接参照に解決します。実行時の実際の型に基づくメソッドの実行バージョン。これは動的ディスパッチと呼ばれます。
仮想マシンの動的ディスパッチの実装
仮想マシンの概念分析のモードには、静的ディスパッチと動的ディスパッチがあります。仮想マシンが仮想マシン「具体的にどうするか」
に違いがあることがわかります。さまざまな仮想マシンの実装:
したがって、仮想マシンの実際の実装では、パフォーマンス上の理由から、ほとんどの実装ではそのような頻繁な検索は実際には実行されません
仮想メソッド テーブル インデックスを使用します。 パフォーマンスを向上させるための
#サブクラスに重複がある場合 このメソッドを記述した後、サブクラス メソッド テーブル内のアドレスは、サブクラスの実際のメソッドを指すエントリ アドレスに置き換えられます
親クラス、サブクラスで同じシグネチャを持つメソッド クラスの仮想メソッド テーブルには同じインデックス番号があります:
クラスの変数の初期値を準備した後、仮想マシンも初期化されます
以上がJava メソッド呼び出しを使用して静的および動的ディスパッチを解決するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。