私の理解では、コンパイル中に型チェックを実行できる場合、型キャストはコンパイル中に実行され、実行時のオーバーヘッドは発生しません。
例えば
public Child getChild() {
Parent o = new Child();
return (Child) o;
}
型キャストはコンパイル中または実行時に行われますか?
また、型キャストをjavacコンパイラで行うかVMで行うかを決定する一般的なルールはありますか?
実際、この場合、3つの可能性があります。
javac
コンパイラーは最適化を実行できます。オプション1または2だと思いますが、これはプラットフォーム固有である可能性があります。
実際、私のシステムでは、バイトコードは最適化されていません。最適化が行われる場合は、JITコンパイラがそれを行います。(これは私が聞いたことと一致します...ほとんどのJavaバイトコードコンパイラは、バイトコードを生成する前に最適化の方法をほとんど実行しません。)
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public Child getChild();
Code:
0: new #16; //class Child
3: dup
4: invokespecial #18; //Method Child."<init>":()V
7: astore_1
8: aload_1
9: checkcast #16; //class Child
12: areturn
}
実行中のプログラムがオブジェクト参照を別のタイプにキャストしようとすると、仮想マシンは、キャスト先のタイプが参照オブジェクトの実際のクラスであるか、そのスーパータイプの1つであるかを確認する必要があります。プログラムがinstanceof操作を実行するときに、同じ種類のチェックを実行する必要があります。
いずれの場合も、仮想マシンは参照されるオブジェクトのクラスデータを調べる必要があります。プログラムがインスタンスメソッドを呼び出すとき、仮想マシンは動的バインディングを実行する必要があります。参照のタイプではなく、オブジェクトのクラスに基づいて呼び出すメソッドを選択する必要があります。これを行うには、オブジェクトへの参照のみが指定されたクラスデータに再度アクセスできる必要があります。
編集:
一部のバインディングが実行時にのみ発生するように、Javaコンパイラはキャストが正しいかどうかをチェックする責任を負いません。Java仮想マシンは、実行時にチェックを実行して、実際の参照オブジェクトが新しいタイプの正当なオブジェクトであるかどうかを確認します。そうでない場合は、実行時例外ClassCastExceptionが発生します。
コンパイルしたとき
public class Test {
public Child getChildVersion1() {
Parent o = new Child();
return (Child) o;
}
public Child getChildVersion2() {
return new Child();
}
}
javap -c Test
Java 7(Windows 7 64ビット)を使用してそのコードを逆コンパイルすると、この結果が得られました
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public Child getChildVersion1();
Code:
0: new #2 // class Child
3: dup
4: invokespecial #3 // Method Child."<init>":()V
7: astore_1
8: aload_1
9: checkcast #2 // class Child
12: areturn
public Child getChildVersion2();
Code:
0: new #2 // class Child
3: dup
4: invokespecial #3 // Method Child."<init>":()V
7: areturn
}
したがって、コンパイラがメソッドgetChildVersion1
を最適化していないgetChildVersion2
ため、コンパイル時に型をチェックする以外に、実行時にチェックすることもできます(9: checkcast #2
)。しかし、Stephen Cが述べたように、それはプラットフォーム(OS、Javaバージョン)に関連している可能性があります。
私はそれが両方の段階で行われたと思います。コンパイル時に、コンパイラーは、強い型の言語のように、型を混同しないように適切なキャストを実行するように強制します。
Object
ただし、たとえば、パラメータとしてString
(実際に存在するオブジェクトに対して機能する)にキャストした場合instanceof
String
でも、JVMは、の実装クラスがObject
本当に拡張されているかどうかを確認する必要があり、そうでない場合String
はを取得します。 ClassCastException
t。
実行時のテストを必要としない変換の場合、コンパイラーが実行時のキャストを回避するためにいくつかの最適化を行う可能性があります。
実行時にテストが必要な変換のタイプについて詳しく知るには、 JLSの第5章「変換とプロモーション」を読むことをお勧めします。
例5.0-1。コンパイル時と実行時の変換
A conversion from type Object to type Thread requires a run-time check to make sure that the run-time value is actually an instance of class Thread or one of its subclasses; if it is not, an exception is thrown.
A conversion from type Thread to type Object requires no run-time action; Thread is a subclass of Object, so any reference produced by an expression of type Thread is a valid reference value of type Object.
A conversion from type int to type long requires run-time sign-extension of a 32-bit integer value to the 64-bit long representation. No information is lost.
A conversion from type double to type long requires a nontrivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost.
このような変換では、実際の参照値が新しいタイプの正当な値であるかどうかを確認するために、実行時にテストが必要です。そうでない場合は、ClassCastExceptionがスローされます。
5.1.8。開箱変換; 変換は実行時に続行されます。
5.5.3も参照してください。実行時にチェックされたキャスト
たとえば、変換がいつ行われたかを判断するのはそれほど簡単ではありません。
public class Main {
private static class Child extends Parent{
public Child() {
}
}
private static class Parent {
public Parent() {
}
}
private static Child getChild() {
Parent o = new Child();
return (Child) o;
}
public static void main(final String[] args) {
Child c = getChild();
}
}
によって与えられる結果javap -c Main
は次のとおりです。
public class Main extends java.lang.Object{
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method getChild:()LMain$Child;
3: astore_1
4: return
}
メソッド宣言をpublic static Child getChild()
結果に変更すると、次のようになります。
public class Main extends java.lang.Object{
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static Main$Child getChild();
Code:
0: new #2; //class Main$Child
3: dup
4: invokespecial #3; //Method Main$Child."<init>":()V
7: astore_0
8: aload_0
9: checkcast #2; //class Main$Child
12: areturn
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method getChild:()LMain$Child;
3: astore_1
4: return
}
アクセサを変更するだけで、可能な最適化に大きな影響を与える可能性があることがわかります。