ないJavaのキャストは、オーバーヘッド紹介しますか?どうして?
-
24-09-2019 - |
質問
私たちは別の型のオブジェクトをキャスト任意のオーバーヘッドはありますか?またはコンパイラだけですべてを解決し、何のコストは、実行時には存在しない?
これは一般的なものか、それとも異なる場合があるのですか?
は、例えば、我々は、各要素は異なるタイプを持っているかもしれないオブジェクト[]の配列を有すると仮定する。私たちは常に要素0はダブルで、言うことを確実に知るしかし、要素1はStringです。 (私はこれは間違っているデザインですが、知っているのは、ちょうど私がこれをしなければならなかったと仮定しましょう。)
であるJavaの型情報は実行時に周りのまま?またはすべてがコンパイル後に忘れられている、と私たちがしなければ(ダブル)の要素は、[0]、私たちは、ポインタをたどると、doubleとしてそれらの8つのバイトを解釈するだろう、それが何であれ?
私はタイプがJavaで行われているかについて非常に不明瞭です。あなたは、書籍や記事のおかげ上の任意のreccommendationを持っている場合は、あまりにます。
解決
鋳造の2種類があります:
:あなたは自動的に行われ、オーバーヘッドが存在していない広いタイプにタイプからキャストすると、の暗黙のの、キャスト
String s = "Cast";
Object o = s; // implicit casting
の明示的なのあなたは、より狭いものに広いタイプから行くとき、キャスト。この場合、明示的にそのようにキャストを使用する必要があります:
Object o = someObject;
String s = (String) o; // explicit casting
二つのタイプがチェックされなければならないと鋳造が可能でない場合には、JVMがClassCastExceptionがスローする必要があるため、この第二の場合は、実行時にオーバーヘッドがあります。
をキャストするとの間で変換するために使用されます 種類 - 参照型の間で 特に、鋳物の種類について 私たちが興味を持っているする動作 ここに。
のアップキャストの操作(とも呼ばれます Javaで拡大変換 言語仕様)コンバートA 祖先へのサブクラスを参照 クラス参照。このキャスティング 動作は以降、通常、自動化され それは常に安全だとすることができ コンパイラによって直接実装ます。
のダウンキャストの操作(とも呼ばれます Javaでの変換を狭めます 言語仕様)変換AN サブクラスへの先祖クラス参照 参照。このキャスト操作 Javaのため、実行オーバーヘッドを作成します キャストがでチェックされている必要があります ランタイムは、それが有効だことを確認します。 参照されたオブジェクトがない場合には ターゲット・タイプのいずれかのインスタンス キャストまたはその型のサブクラス、 未遂キャストが許可されていません そしてスローする必要があります java.lang.ClassCastExceptionが。
他のヒント
のJavaの合理的な実装を示します。
各オブジェクトは、とりわけ、ランタイム型へのポインタを含むヘッダを有する(インスタンスDouble
またはString
ために、それはCharSequence
又はAbstractList
することができませんでした)。ランタイムコンパイラ(Sunの場合は、一般的にホットスポット)を仮定すると、静的に生成されたマシンコードが実行されるいくつかのチェックの必要性をタイプを決定することはできません。
ランタイム型ニーズへのポインタが読み取られることを第一に。これは、とにかく似たような状況で仮想メソッドを呼び出すために必要です。
クラス型にキャストするために、あなたがjava.lang.Object
を打つまであります正確にどのように多くのスーパー知られているので、型がポインタ型(ホットスポットでは実際には最初の8)から一定のオフセットで読み取ることができます。再び、これは、仮想メソッドのためのメソッドポインタを読み出すと類似している。
次に読み出し値はちょうどキャストの予想静的な型との比較が必要です。命令セット・アーキテクチャに応じて、別の命令が間違った枝に枝(または障害)にする必要があります。このような32ビットARMなどのISAは、条件付き命令を有し、幸せ経路を介して悲しい経路パスを有することができるかもしれない。
インターフェースは、インターフェースの多重継承のために、より困難です。一般インターフェースへの最後の2人のキャストは、実行時の型にキャッシュされます。 (十年以上前)の非常に初期の頃は、インタフェースは少し遅いなかったが、それはもはや関係があります。
うまくいけば、この種のものは、パフォーマンスへの大部分は無関係であることがわかります。あなたのソースコードは、より重要です。パフォーマンスの面では、あなたのシナリオにおける最大のヒットは、(タイプ情報はもちろん、一般的になります)あらゆる場所にオブジェクトポインタを追いかけてから、キャッシュミスをする傾向がある。
は、例えば、我々は、各要素は異なるタイプを持っているかもしれないオブジェクト[]の配列を有すると仮定する。私たちは常に要素0はダブルで、言うことを確実に知るしかし、要素1はStringです。 (私はこれは間違っているデザインですが、知っているのは、ちょうど私がこれをしなければならなかったと仮定しましょう。)
コンパイラは、アレイの個々の要素の種類に注意しません。また各要素の式の型が配列の要素型に割り当て可能であることを単にチェックする。
であるJavaの型情報は実行時に周りのまま?またはすべてがコンパイル後に忘れられている、と私たちがしなければ(ダブル)の要素は、[0]、私たちは、ポインタをたどると、doubleとしてそれらの8つのバイトを解釈するだろう、それが何であれ?
一部の情報は、個々の要素の静的な型の実行時に周りに保たれますが、されていません。あなたは、クラスファイル形式を見てからこれを伝えることができます。
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);
}
}
}
Point[] pa = cpa
ColoredPoint
がポイントのサブクラスですが、pa[0] = new Point()
が有効でないため、有効です。
これは、ジェネリック型に対向しています。コンパイラ挿入checkcast
命令、必要に応じています。
ジェネリック型と配列のタイピングのこの差は、配列やジェネリック型を混在させることがしばしば不適当ます。
理論的には、オーバーヘッドが導入されます。 しかし、現代のJVMは、スマートです。 各実装は異なっているが、JITが、それは競合が発生することはないことを保証することができたときにチェックをキャスト離れて最適化されたことを実装が存在する可能性があることを前提とするのは無理ではありません。 そのため、特定のJVMがこれを提供するように、私はあなたを伝えることができませんでした。私はJITの最適化自分の詳細を知りたいのです認めなければならないが、これらは心配するJVMのエンジニアのためのものです。
この話の教訓は、最初に理解しやすいコードを書くことです。あなたはスローダウン、プロファイルを経験し、あなたの問題を特定している場合。 オッズは、それが鋳造によるものでないことを良いです。 あなたはTOをNEED判明するまで、それを最適化する試みで、クリーンで安全なコードを犠牲にしないでください。