静的および動的バインディングが実際にどのように機能するかを理解するために、またはそれらはコンパイラとJVMによってどのように識別されますか?
以下にMammal
、メソッドspeak()
とHuman
クラスを持つ親クラスがあり、クラスが拡張されMammal
、speak()
メソッドがオーバーライドされ、再度でオーバーロードされる例を見てみましょうspeak(String language)
。
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
// Valid overload of speak
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
// Code below contains the output and bytecode of the method calls
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
上記のコードをコンパイルしjavap -verbose OverridingInternalExample
、を使用してバイトコードを確認しようとすると、コンパイラーが定数テーブルを生成することがわかります。ここで、すべてのメソッド呼び出しに整数コードを割り当て、プログラム自体のバイトコード(プログラム自体に含まれている)を割り当てます(すべてのメソッド呼び出しの下のコメントを参照してください)
上記のコードを見て、私たちはのバイトコードことを確認することができhumanMammal.speak()
、human.speak()
かつhuman.speak("Hindi")
全く異なっている(invokevirtual #4
、invokevirtual #7
、invokevirtual #9
)コンパイラは引数リストとクラス参照に基づいて、それらを区別することができますので。これらはすべてコンパイル時に静的に解決されるため、メソッドのオーバーロードが静的ポリモーフィズムまたは静的バインディングとして知られています。
しかし、コンパイラーによると両方のメソッドが参照で呼び出されるため、anyMammal.speak()
とのバイトコードhumanMammal.speak()
は同じです(invokevirtual #4
)Mammal
。
では、両方のメソッド呼び出しのバイトコードが同じである場合、JVMはどのメソッドを呼び出すかをどのようにして知るのでしょうか。
まあ、答えはバイトコード自体に隠されており、それはinvokevirtual
命令セットです。JVMは、このinvokevirtual
命令を使用して、C ++仮想メソッドに相当するJavaを呼び出します。C ++では、別のクラスの1つのメソッドをオーバーライドする場合は、それを仮想として宣言する必要がありますが、Javaでは、子クラスのすべてのメソッドをオーバーライドできるため、Javaではすべてのメソッドがデフォルトで仮想です(private、final、staticメソッドを除く)。
Javaでは、すべての参照変数が2つの隠しポインタを保持します
- オブジェクトのメソッドを再び保持するテーブルへのポインターとClassオブジェクトへのポインター。例[speak()、speak(String)Classオブジェクト]
- インスタンス変数の値など、そのオブジェクトのデータ用にヒープに割り当てられたメモリへのポインター。
したがって、すべてのオブジェクト参照は、そのオブジェクトのすべてのメソッド参照を保持するテーブルへの参照を間接的に保持します。JavaはC ++からこの概念を借用しており、このテーブルは仮想テーブル(vtable)として知られています。
vtableは、仮想メソッド名とそれらの配列インデックスへの参照を保持する構造体のような配列です。JVMは、クラスをメモリにロードするときに、クラスごとに1つのvtableのみを作成します。
したがって、JVMがinvokevirtual
命令セットに遭遇するたびに、そのクラスのvtableでメソッド参照をチェックし、特定のメソッド(この場合は参照ではなくオブジェクトからのメソッド)を呼び出します。
これらすべてが実行時にのみ解決され、実行時にJVMが呼び出すメソッドを認識できるため、メソッドのオーバーライドが動的ポリモーフィズムまたは単にポリモーフィズムまたはダイナミックバインディングと呼ばれるのはこのためです。
詳細については、私の記事「JVMによるメソッドのオーバーロードとオーバーライドの内部処理」を参照してください。