114

あるタイプのオブジェクトを別のタイプにキャストするときにオーバーヘッドはありますか? それとも、コンパイラがすべてを解決するだけで、実行時のコストはかかりませんか?

これは一般的なことですか、それとも異なる場合がありますか?

たとえば、Object[] の配列があり、各要素が異なる型を持っているとします。しかし、たとえば、要素 0 が Double であり、要素 1 が String であることは常に確実にわかっています。(これが間違った設計であることはわかっていますが、これを行う必要があったと仮定しましょう。)

Java の型情報は実行時に保持されますか? または、コンパイル後にすべてが忘れられ、(Double)elements[0] を実行すると、ポインターをたどって、それらの 8 バイトを double として解釈します。

Java で型がどのように処理されるかについては、よくわかりません。おすすめの本や記事があれば、それもよろしくお願いします。

4

5 に答える 5

85

キャストには次の 2 種類があります。

型からより広い型にキャストする場合の暗黙的なキャスト。これは自動的に行われ、オーバーヘッドはありません。

String s = "Cast";
Object o = s; // implicit casting

より広いタイプからより狭いタイプに移行する場合の明示的なキャスト。この場合、次のようにキャストを明示的に使用する必要があります。

Object o = someObject;
String s = (String) o; // explicit casting

この 2 番目のケースでは、実行時にオーバーヘッドが発生します。これは、2 つの型をチェックする必要があり、キャストが実行できない場合、JVM は ClassCastException をスローする必要があるためです。

JavaWorldから取得: キャストのコスト

キャストは、型間の変換、特に参照型間の変換に使用されます。ここで関心のある型のキャスト操作についてです。

アップキャスト操作 (Java 言語仕様では拡大変換とも呼ばれます) は、サブクラス参照を祖先クラス参照に変換します。このキャスト操作は、常に安全であり、コンパイラによって直接実装できるため、通常は自動的に行われます。

ダウンキャスト操作 (Java 言語仕様ではナローイング変換とも呼ばれます) は、祖先クラスの参照をサブクラスの参照に変換します。Java では、キャストが有効であることを確認するために実行時にチェックする必要があるため、このキャスト操作により実行オーバーヘッドが生じます。参照されたオブジェクトがキャストのターゲット型またはその型のサブクラスのインスタンスでない場合、試行されたキャストは許可されず、java.lang.ClassCastException をスローする必要があります。

于 2010-01-31T07:14:42.933 に答える
44

Java の合理的な実装の場合:

各オブジェクトには、特に実行時の型へのポインターを含むヘッダーがあります (たとえばDoubleorですが、 orStringになることはありません)。ランタイム コンパイラ (通常、Sun の場合は HotSpot) が型を静的に判断できないと仮定すると、生成されたマシン コードでいくつかのチェックを実行する必要があります。CharSequenceAbstractList

まず、ランタイム型へのポインターを読み取る必要があります。とにかく、これは同様の状況で仮想メソッドを呼び出すために必要です。

クラス型へのキャストの場合、ヒットするまでスーパークラスの数が正確にわかっているjava.lang.Objectため、型ポインターから一定のオフセット (実際には HotSpot の最初の 8 つ) で型を読み取ることができます。これも、仮想メソッドのメソッド ポインターを読み取ることに似ています。

次に、読み取られた値は、キャストの予想される静的型との比較のみが必要です。命令セットのアーキテクチャによっては、別の命令が不適切な分岐で分岐 (またはフォールト) する必要があります。32 ビット ARM などの ISA には条件付き命令があり、サッド パスがハッピー パスを通過できる場合があります。

インターフェイスの多重継承により、インターフェイスはより困難になります。通常、インターフェイスへの最後の 2 つのキャストは、ランタイム型にキャッシュされます。初期の頃 (10 年以上前)、インターフェースは少し遅かったですが、それはもはや関係ありません。

この種のことは、パフォーマンスとはほとんど無関係であることがお分かりいただけると思います。ソースコードの方が重要です。パフォーマンスに関しては、シナリオでの最大のヒットは、あらゆる場所でオブジェクト ポインターを追跡することによるキャッシュ ミスである可能性があります (型情報はもちろん一般的です)。

于 2010-01-31T11:29:21.620 に答える
8

たとえば、Object[] の配列があり、各要素が異なる型を持っているとします。しかし、たとえば、要素 0 が Double であり、要素 1 が String であることは常に確実にわかっています。(これが間違った設計であることはわかっていますが、これを行う必要があったと仮定しましょう。)

コンパイラは、配列の個々の要素の型を認識しません。各要素式の型が配列要素型に割り当て可能であることを確認するだけです。

Java の型情報は実行時に保持されますか? または、コンパイル後にすべてが忘れられ、(Double)elements[0] を実行すると、ポインターをたどって、それらの 8 バイトを double として解釈します。

一部の情報は実行時に保持されますが、個々の要素の静的な型は保持されません。これは、クラス ファイルの形式を見ればわかります。

理論的には、JIT コンパイラーが「エスケープ分析」を使用して、一部の割り当てで不要な型チェックを排除できる可能性があります。ただし、これをあなたが提案している程度まで行うことは、現実的な最適化の範囲を超えています。個々の要素のタイプを分析するメリットは小さすぎます。

その上、人々はとにかくそのようなアプリケーション コードを書くべきではありません。

于 2010-01-31T07:37:53.413 に答える
6

実行時にキャストするためのバイトコード命令を と呼び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 = cpaColoredPointPoint のサブクラスであるため有効ですが、pa[0] = new Point()有効ではありません。

これは、実行時に型情報が保持されないジェネリック型とは対照的です。checkcastコンパイラは、必要に応じて命令を挿入します。

ジェネリック型と配列の型付けのこの違いにより、多くの場合、配列とジェネリック型を混在させることは不適切になります。

于 2010-01-31T07:55:21.493 に答える
3

理論的には、オーバーヘッドが発生します。ただし、最新の JVM はスマートです。実装はそれぞれ異なりますが、競合が発生しないことが保証されている場合に、JIT がキャスト チェックを最適化する実装が存在する可能性があると想定するのは不合理ではありません。どの特定の JVM がこれを提供しているかはわかりません。私自身、JIT 最適化の詳細を知りたいと思っていることは認めざるを得ませんが、これらは JVM エンジニアが心配することです。

この話の教訓は、最初に理解できるコードを書くことです。スローダウンが発生している場合は、プロファイリングして問題を特定します。キャストによるものではない可能性が高いです。最適化する必要があることがわかるまで、クリーンで安全なコードを犠牲にしないでください。

于 2016-11-10T18:07:21.240 に答える