5

次のコードがあります。

public class Parent {

    @Override
    public int hashCode() {
         return 0;
    }

}

public class Child extends Parent {

    public void test() {
        this.toString();
        this.hashCode();
    }

}

上記のコードでわかるように、Child は Object から toString() を継承し、Parent から hashCode() を継承します。Child#test のバイトコード操作は以下の通りです。

ALOAD 0: this
INVOKEVIRTUAL Object.toString() : String
ALOAD 0: this
INVOKEVIRTUAL Child.hashCode() : int
RETURN

invokevirtual が Object.toString() を呼び出す場合、一貫性のために Parent.hashCode() を呼び出す必要があると思います。または、Child.hashCode() が呼び出された場合は、Child.toString() を呼び出す必要があります。

ただし、invokevirtual は、ターゲット メソッドが Object によって継承されている場合に限り、一貫性を維持しません。

その場合のみ、invokevirtual はオブジェクト内のメソッドを呼び出します。それ以外の場合は、invokevirtual が現在のクラスのメソッドを呼び出します。

なぜこれが起こるのか知りたいです。

4

3 に答える 3

5

JVM仕様によるとp。3.7

コンパイラは、クラスインスタンスの内部レイアウトを認識していません。代わりに、実行時定数プールに格納されているインスタンスのメソッドへのシンボリック参照を生成します。これらの実行時定数プール項目は、実際のメソッドの場所を決定するために実行時に解決されます。

これは、これらのシンボリックChild.hashCode()がすべて定数であり、JVMがこのメソッドを呼び出す方法を指定していないことを意味します。toString()メソッドコンパイラは、このメソッドの基本実装がクラスにあることを知っているようですObject。したがって、シンボリック定数をObject定数プールのクラスに配置します。これは、JVM用のコンパイラを作成するある種の最適化です。

  Constant pool:
const #2 = Method   #24.#25;    //  java/lang/Object.toString:()Ljava/lang/String;
...
const #24 = class   #34;    //  java/lang/Object
const #25 = NameAndType #35:#36;//  toString:()Ljava/lang/String;
于 2013-03-02T09:52:14.797 に答える
4

コンパイラが非論理的に動作することは正しいです。ただし、このコードの効果は、提案した両方のバリアントの効果と同じです。したがって、これはおそらく意図的な動作ではなく、コンパイラのコードの長い進化の結果です。他のコンパイラでは異なるコードが生成される場合があります。

于 2013-03-02T08:59:26.827 に答える
1

私の理論:toString()は非常に頻繁に使用されるため、javac は共通を使用してObject.toString()定数プール エントリを保存します。

たとえば、コードに が含まれている場合、コンタント プールには 2 つのエントリではなくfoo.toString() and bar.toString()1 つのみが必要です。Object.toStringFoo.toString and Bar.toString

Javac は、コードを分析して本当に必要かどうかを確認する代わりに、おそらくこの最適化をハードコーディングしました。

于 2013-03-02T17:32:41.320 に答える