この単純なJavaクラスについて考えてみます。
class MyClass {
public void bar(MyClass c) {
c.foo();
}
}
c.foo()行で何が起こるかについて説明したいと思います。
元の誤解を招く質問
注:これのすべてが実際に個々のinvokevirtualオペコードで発生するわけではありません。ヒント:Javaメソッドの呼び出しを理解したい場合は、invokevirtualのドキュメントだけを読んではいけません。
バイトコードレベルでは、c.foo()の要点はinvokevirtualオペコードになり、invokevirtualのドキュメントによると、多かれ少なかれ次のことが起こります。
- コンパイル時クラスMyClassで定義されているfooメソッドを検索します。(これには、最初にMyClassを解決することが含まれます。)
- 次のようないくつかのチェックを行います。cが初期化メソッドではないことを確認し、MyClass.fooの呼び出しが保護された修飾子に違反しないことを確認します。
- 実際に呼び出すメソッドを見つけます。特に、cのランタイムタイプを検索します。そのタイプにfoo()がある場合は、そのメソッドを呼び出して戻ります。そうでない場合は、cのランタイムタイプのスーパークラスを検索します。そのタイプにfooがある場合は、そのメソッドを呼び出して戻ります。そうでない場合は、cの実行時型のスーパークラスのスーパークラスを検索します。そのタイプにfooがある場合は、そのメソッドを呼び出して戻ります。など。適切な方法が見つからない場合は、エラーが発生します。
ステップ3だけで、呼び出すメソッドを見つけ出し、そのメソッドが正しい引数/戻り型を持っていることを確認するのに十分なようです。だから私の質問は、なぜステップ#1が最初に実行されるのかということです。考えられる答えは次のようです。
- ステップ1が完了するまで、ステップ3を実行するための十分な情報がありません。(これは一見信じられないようですので、説明してください。)
- #1と#2で行われるリンクまたはアクセス修飾子のチェックは、特定の悪いことが起こらないようにするために不可欠であり、これらのチェックは、実行時型階層ではなく、コンパイル時型に基づいて実行する必要があります。(説明してください。)
改訂された質問
行c.foo()のjavacコンパイラ出力のコアは、次のような命令になります。
invokevirtual i
ここで、iはMyClassのランタイム定数プールへのインデックスです。その定数プールエントリは、タイプCONSTANT_Methodref_infoであり、(おそらく間接的に)A)呼び出されるメソッドの名前(つまり、foo)、B)メソッドのシグネチャ、およびC)メソッドが呼び出されるコンパイル時クラスの名前を示します。 on(つまり、MyClass)。
問題は、なぜコンパイル時型(MyClass)への参照が必要なのかということです。invokevirtualは実行時型cで動的ディスパッチを実行するので、コンパイル時クラスへの参照を格納するのは冗長ではありませんか?