リフレクションを使用していますか?もしそうなら、舞台裏で何が起こっていますか?
2 に答える
Jon は、演算子がバイトコードにどのようにマップされるかについて正しいです。実装に関する限り、ほとんどの JVM はメモリ内のオブジェクトを、ロードされた具象クラスのタグ付き共用体として表します。
タグ付き共用体は、バリアント、バリアント レコード、判別共用体、非結合共用体、または合計型とも呼ばれ、いくつかの異なるが固定された型を取ることができる値を保持するために使用されるデータ構造です。
そのため、具象型がクラス型のインスタンスである場合にビットが設定されたスパースブール行列x instanceof MyClassType
を調べることで答えることができます。
x instanceof InterfaceType
は少しトリッキーですが、同様の方法でそれを解決することもできます。
JVM は、名目上の型 (クラスまたはインターフェイス型) ごとに行を 1 つ、クラス型ごとに列を 1 つ持つ大規模なスパース マトリックスをメモリ内に保持できます。
例えば:
[all nominal types]
Object String Integer Number Comparable Iterable ...
[only String ✓ ✓ ✓
concrete Integer ✓ ✓ ✓ ✓
types] ...
JVM がクラスをガベージ コレクションする必要がある場合、このマトリックスを維持するのが難しくなるため、通常はクラス オブジェクトを含む行を格納します。
プロキシ クラスは興味深い特殊なケースですが、私の推測では、プロキシ クラスの定義には、実行時に何らかのバイトコードを生成し、その後、ほとんどの JVM で通常のクラス ローディング システムを通過させるというものがあります。
基本的に、これは JVM 命令セットの一部です。特定のinstanceof
命令があります。たとえば、次のようなメソッドです。
public static void checkString(Object x) {
if (x instanceof String) {
System.out.println("Foo");
}
}
次のようにコンパイルされます。
public static void checkString(java.lang.Object);
Code:
0: aload_0
1: instanceof #2 // class java/lang/String
4: ifeq 15
7: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #4 // String Foo
12: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: return
}
(それは単なる出力ですjavap
。)
JVM 仕様には、命令が何をしなければならないかの詳細があります。命令の正確な詳細については、セクション 6.5を参照してください。実装方法は VM の実装次第ですが、実装例の 1 つとして次のようなものがあります。
- 最初のオペランドが であるかどうかを確認します
null
(そうであれば戻りfalse
ます) - 最初のオペランドが参照するオブジェクトの実行時の型を見つけます。
- 実際の型が 2 番目のオペランドと互換性があるかどうかを証明できるまで、型階層 (実装されたインターフェイスを含む) を上に移動します。