あるタイプのオブジェクトを別のタイプにキャストするときにオーバーヘッドはありますか?または、コンパイラはすべてを解決するだけで、実行時にコストはかかりませんか?
これは一般的なことですか、それとも別のケースがありますか?
たとえば、Object []の配列があり、各要素の型が異なるとします。しかし、たとえば、要素0がDoubleであり、要素1がStringであることが常に確実にわかります。 (私はこれが間違った設計であることを知っていますが、私はこれをしなければならないと仮定しましょう。)
Javaの型情報は実行時に引き続き保持されますか?または、コンパイル後にすべてが忘れられ、(Double)elements [0]を実行した場合、ポインタをたどって8バイトをdoubleとして解釈します。
Javaで型がどのように行われるかについては非常にわかりません。書籍や記事に関するお勧めがある場合は、ありがとうございます。
キャストには2つのタイプがあります。
Implicitキャスト、型からより広い型へのキャスト。これは自動的に行われ、オーバーヘッドはありません。
String s = "Cast";
Object o = s; // implicit casting
明示的なキャスト。幅の広いタイプから幅の狭いタイプに移動する場合。この場合、次のようなキャストを明示的に使用する必要があります。
Object o = someObject;
String s = (String) o; // explicit casting
この2番目のケースでは、2つのタイプをチェックする必要があるため、実行時にオーバーヘッドがあり、キャストが実行できない場合、JVMはClassCastExceptionをスローする必要があります。
JavaWorld:キャストのコスト から取得
Castingは、タイプ間、特に参照タイプ間での変換に使用されます。ここで関心のあるキャスト操作のタイプに対して。
Upcast操作(Java Language Specification)の拡張変換とも呼ばれます)は、サブクラス参照を祖先クラス参照に変換します。このキャスト操作は、常に安全であり、コンパイラによって直接実装できるため、通常は自動です。
Downcast操作(Java言語仕様)のナローイング変換とも呼ばれます)は、先祖クラス参照をサブクラス参照に変換します。 Javaは、実行時にキャストをチェックして、それが有効であることを確認する必要があるため。参照オブジェクトがキャストのターゲットタイプまたはそのタイプのサブクラスの場合、試行されたキャストは許可されず、Java.lang.ClassCastExceptionをスローする必要があります。
Javaの合理的な実装の場合:
各オブジェクトには、特にランタイム型へのポインターを含むヘッダーがあります(たとえば、Double
またはString
ですが、CharSequence
またはAbstractList
)。ランタイムコンパイラ(一般的にSunの場合はHotSpot)が型を静的に決定できないと仮定すると、生成されたマシンコードによって実行される必要があるチェックがあります。
最初に、ランタイム型へのポインタを読み取る必要があります。これは、とにかく同様の状況で仮想メソッドを呼び出すために必要です。
クラス型へのキャストでは、Java.lang.Object
に到達するまでにスーパークラスがいくつあるかが正確にわかっているので、型は型ポインター(実際にはHotSpotの最初の8)から一定のオフセットで読み取ることができます。繰り返しますが、これは仮想メソッドのメソッドポインターを読み取ることに似ています。
次に、読み取り値は、キャストの予想される静的タイプとの比較を必要とするだけです。命令セットのアーキテクチャによっては、別の命令が誤った分岐で分岐(または障害)する必要があります。 32ビットARMなどのISAには条件付き命令があり、悲しいパスをハッピーパスに渡すことができる場合があります。
インターフェースの多重継承のため、インターフェースはより困難です。通常、インターフェイスへの最後の2つのキャストはランタイムタイプにキャッシュされます。ごく初期(10年以上前)では、インターフェースは少し遅かったのですが、それはもはや関係ありません。
うまくいけば、この種のことはパフォーマンスとほとんど無関係であることがわかります。あなたのソースコードはより重要です。パフォーマンスの面では、シナリオでの最大のヒットは、至る所でオブジェクトポインターを追跡することによるキャッシュミスの可能性が高いことです(もちろん、型情報は一般的です)。
たとえば、Object []の配列があり、各要素の型が異なる場合があります。しかし、たとえば、要素0がDoubleであり、要素1がStringであることが常に確実にわかります。 (私はこれが間違った設計であることを知っていますが、私はこれをしなければならないと仮定しましょう。)
コンパイラは、配列の個々の要素のタイプに注意しません。各要素式の型が配列要素型に割り当て可能であることを確認するだけです。
Javaの型情報は実行時に引き続き保持されますか?または、コンパイル後にすべてが忘れられ、(Double)elements [0]を実行した場合、ポインタをたどって、その8バイトをdoubleとして解釈します。
一部の情報は実行時に保持されますが、個々の要素の静的タイプは保持されません。これは、クラスファイル形式を見ればわかります。
理論的には、JITコンパイラが「エスケープ分析」を使用して、一部の割り当てで不要な型チェックを排除できる可能性があります。ただし、提案している程度までこれを行うと、現実的な最適化の範囲を超えてしまいます。個々の要素のタイプを分析することの見返りは小さすぎます。
それに、人々はとにかくそのようなアプリケーションコードを書くべきではありません。
実行時にキャストを実行するためのバイトコード命令は、checkcast
と呼ばれます。生成される命令を確認するには、javap
を使用してJavaコードを逆アセンブルできます。
配列の場合、Javaは実行時に型情報を保持します。ほとんどの場合、コンパイラは型エラーをキャッチしますが、次の場合にArrayStoreException
に遭遇する場合がありますオブジェクトを配列に保存しようとしましたが、型が一致しません(そしてコンパイラはそれをキャッチしませんでした) Java言語仕様 は次の例を示します。
_class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
ColoredPoint[] cpa = new ColoredPoint[10];
Point[] pa = cpa;
System.out.println(pa[1] == null);
try {
pa[0] = new Point();
} catch (ArrayStoreException e) {
System.out.println(e);
}
}
}
_
ColoredPoint
はPointのサブクラスであるため、_Point[] pa = cpa
_は有効ですが、pa[0] = new Point()
は無効です。
これは、実行時に型情報が保持されないジェネリック型とは対照的です。コンパイラは、必要に応じてcheckcast
命令を挿入します。
ジェネリック型とジェネリック型の入力のこの違いにより、配列とジェネリック型を混在させることはしばしば不適当になります。
理論的には、オーバーヘッドが発生します。ただし、最新のJVMはスマートです。実装はそれぞれ異なりますが、競合がないことを保証できる場合にJITがキャストチェックを最適化する実装が存在する可能性があると考えるのは不合理ではありません。どの特定のJVMがこれを提供しているかについて、私はあなたに伝えることができなかった。私は自分でJIT最適化の詳細を知りたいと認めなければなりませんが、これらはJVMエンジニアが心配することです。
物語の教訓は、理解可能なコードを最初に書くことです。速度が低下している場合は、問題をプロファイリングして特定します。オッズは、キャストによるものではないというのは良いことです。あなたが知っている必要があるまで最適化するために、クリーンで安全なコードを犠牲にしないでください。