javacが不可能なキャストを許可し、他のキャストを許可しないのはなぜですか?


52

をにキャストしようとStringするjava.util.Dateと、Javaコンパイラがエラーをキャッチします。それでは、コンパイラが以下のエラーをフラグしないのはなぜですか?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

もちろん、JVMはClassCastException実行時にをスローしますが、コンパイラーはフラグを立てません。

動作は、javac 1.8.0_212および11.0.2と同じです。


2
Listここで特別なことはありません。Date d = (Date) new Object();
エリオットフリッシュ

1
最近アルドゥイーノで遊んでいます。キャストを喜んで受け入れず、完全に予測不可能な結果を​​もたらすコンパイラが大好きです。文字列から整数へ?確実なこと!整数から倍精度?かしこまりました!文字列をブール値に?少なくともその1つは主に誤りになります...
Stian Yttervik

@ElliottFrisch:DateとObjectの間には明らかな継承関係がありますが、DateとListの間には関係がありません。したがって、コンパイラがこのキャストにフラグを付けることを期待しました。これは、文字列から日付へのキャストにフラグを付けるのと同じ方法です。しかしZabuzaが優れた答えで説明しているように、Listはインターフェイスであるため、strListListを実装するクラスのインスタンスである場合、キャストは正当です。
Mike Woinoski

これは頻繁に繰り返し発生する質問であり、私はそれの複数の重複を見たと確信しています。それは基本的に強く関連の逆バージョンです:stackoverflow.com/questions/21812289/...
ハルク

1
@StianYttervik -fpermissiveはそれを実行していることです。コンパイラの警告をオンにします。
ボブスバーナー

回答:


86

キャスト技術的に可能です。あなたの場合はそうではないことをjavacで簡単に証明することはできません。JLSはこれを有効なJavaプログラムとして実際に定義しているため、エラーのフラグ付けは正しくありません。

これはListがインターフェイスであるためです。したがってDate、実際にここのようにList偽装して実装するaのサブクラスを作成し、ListそれをキャストしてDate完全に問題ないようにすることができます。例えば:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

その後:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

そのようなシナリオを検出することは常に可能であるとは限りません。インスタンスがたとえばメソッドからのものである場合は、ランタイム情報が必要になるためです。そして、たとえそれがあったとしても、それはコンパイラーにとってはるかに多くの努力を必要とするでしょう。コンパイラーは、クラスツリーがまったく一致する方法がないために絶対に不可能であるキャストのみを防止します。見られるように、ここではそうではありません。

JLSでは、コードが有効なJavaプログラムである必要があることに注意してください。では5.1.6.1。ナローイング参照変換を許可します:

次のすべて当てはまる場合、参照型から参照型Sへのナローイング参照変換が存在しますT

  • [...]
  • 次例が適用されます
    • [...]
    • Sはインターフェース型でTあり、クラス型でありTfinalクラスに名前を付けません。

そのため、コンパイラあなたのケースが実際には不可能であると証明できたとしても、JLSがそれを有効なJavaプログラムとして定義しているため、エラーにフラグを立てることはできません。

警告の表示のみが許可されます。


16
そして、注目に値するのは、それがStringのケースをキャッチする理由は、Stringがfinalであるため、クラスがそれを拡張できないことをコンパイラが認識していることです。
MTilsted

5
実際、myDate = (Date) myString失敗するのはStringの「最終性」ではないと思います。JLSの用語を使用して、ステートメントはSString)からT()への変換を試みDateます。ここでSは、はインターフェースタイプではないため、上記のJLS条件は適用されません。例として、カレンダーを日付にキャストしようとすると、どちらのクラスも最終ではないにもかかわらず、コンパイラエラーが発生します。
Mike Woinoski

1
失望するかどうか、コンパイラーが十分な静的分析を行ってstrListがArrayList型になり得ないことを証明できないかどうかはわかりません。
ジョシュア

3
コンパイラーはチェックを禁止されていません。しかし、それをエラーと呼ぶことは禁じられています。それはコンパイラを非準拠にします。(私の答えを参照してください...)
スティーブンC

3
少し専門用語を追加するには、コンパイラは型Date & List無人であることを証明する必要があります。現在無人であることを証明するだけでは十分ではありません(将来的には存在する可能性があります)。
Polygnome

15

あなたの例の一般化を考えてみましょう:

List<String> strList = someMethod();       
Date d = (Date) strList;

Date d = (Date) strList;コンパイルエラーではない主な理由は次のとおりです。

  • 直感的な理由は、コンパイラが(一般的に)そのメソッドの呼び出しによって返されたオブジェクトの正確な種類を知らないということです。を実装するクラスであるListだけでなく、のサブクラスである可能性ありDateます。

  • 技術的な理由は、 Java言語仕様は「できます」ということです狭め参照変換このタイプのキャストに対応していることを。JLS 5.1.6.1によると:

    「次のすべてが当てはまる場合、参照タイプから参照タイプSへのナローイング参照変換が存在しTます。」

    ...

    5)" Sはインターフェース型でTあり、クラス型でありTfinalクラスに名前を付けません。"

    ...

    別の場所では、JLSは実行時に例外がスローされる可能性があるとも述べています...

    JLS 5.1.6.1の決定は、実際の実行時の型ではなく、関係する変数の宣言れた型にのみ基づいていることに注意してください。一般的なケースでは、コンパイラーは実際のランタイム・タイプを知りませんし、知ることもできません。


では、なぜキャストが機能しないというJavaコンパイラが機能しないのでしょうか。

  • 私の例では、someMethod呼び出しはさまざまなタイプのオブジェクトを返す可能性があります。コンパイラがメソッド本体を分析して、返される可能性のある型の正確なセットを決定できたとしても、それを変更して別の型を返すように誰かを止める方法はありません...それを呼び出すコードをコンパイルした後。これが、JLS 5.1.6.1が言うとおりの基本的な理由です。

  • あなたの例では、スマートコンパイラはキャストが決して成功しないことを理解できます。そして、問題を指摘するためにコンパイル時の警告を発することが許可されています。

とにかく、スマートコンパイラがこれをエラーと見なすことが許可されていないのはなぜですか。

  • これは有効なプログラムであるとJLSが述べているためです。限目。これをエラーと呼んだコンパイラはJavaに準拠していません。

  • また、JLSおよび他のコンパイラーが有効であると言うJavaプログラムを拒否するコンパイラーは、Javaソース・コードの移植性を妨げます。


4
呼び出しクラスをコンパイルした後、呼び出された関数の実装が変わる可能性があるという事実に賛成してください。コンパイル時にそれが証明できるとしても、呼び出し先の現在の実装では、キャストが不可能であるということは、後の実行時にはそうはないかもしれません。呼び出し先が変更または置き換えられたとき。
ピーター-

2
コンパイラーが賢くなりすぎた場合に生じる移植性の問題を強調するための賛成票を投じます。
Mike Woinoski

2

5.5.1。参照タイプのキャスト:

コンパイル時の参照タイプS(ソース)とコンパイル時の参照タイプT(ターゲット)を指定すると、以下のルールによるコンパイル時エラーが発生しない場合、キャスト変換はからSに 存在しTます。

[...]

Sがインターフェイスタイプの場合:

  • [...]

  • 場合T、最終ではないクラスまたはインタフェースタイプであり、次いで、スーパータイプが存在する場合XT、及びスーパータイプYのは S、そのような両方ことXY証明可能異なるパラメータ化された型であり、の消去それXY同じであり、コンパイル時エラー発生します。

    それ以外の場合、キャストはコンパイル時に常に有効です(Tが実装されていなくてもSTmightのサブクラスであるため)。

List<String>であるSDateされT、あなたのケースで。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.