私はこのクラスを持っています:
_class MyClass<N extends Number> {
N n = (N) (new Integer(8));
}
_
そして、私はこれらの出力を得たいです:
_System.out.println(new MyClass<Long>().n);
System.out.println(new MyClass<Long>().n.getClass());
_
最初のSystem.out.println()
ステートメントの出力:
_8
_
2番目のSystem.out.println()
ステートメントの出力:
_Java.lang.ClassCastException: Java.lang.Integer (in module: Java.base)
cannot be cast to Java.lang.Long (in module: Java.base)
_
なぜ最初の出力が出るのですか?キャストもいないのですか? 2番目の出力で例外が発生するのはなぜですか?
PS:私はJava 9を使用しています; JShellで試してみましたが、両方の出力で例外が発生しました。次にIntelliJ IDEで試して、最初の出力ですが、2番目の例外です。
IntelliJが示す動作は私には明らかです。
MyClass
に未チェックのキャストがあります。つまり、次の行を実行すると、new Integer(8)
はLong
にすぐにキャストされず、消去Number
(機能します)にキャストされます:N n =(N)(new Integer(8));
次に、出力ステートメントを見てみましょう。
_System.out.println(new MyClass<Long>().n);
_
要約するとString.valueOf(new MyClass<Long>().n)
-> _((Object)new MyClass<Long>().n).toString()
_になり、nはObject
を介してアクセスされ、toString()
メソッドは静的型Object
を介してアクセスされるため、Long
へのキャストはありません発生します。 new MyClass<Long>().n.toString()
は静的型Long
を介してアクセスされるため、toString()
は例外で失敗します。したがって、タイプLong
occursへのnのキャストは不可能です(Integer
をLong
にキャストすることはできません)。
2番目のステートメントを実行すると、同じことが起こります。
_System.out.println(new MyClass<Long>().n.getClass());
_
getClass
型のObject
メソッド(Long
で宣言)は、静的型Long
を介してアクセスされます。したがって、タイプLong
へのnのキャストが発生し、キャスト例外が発生します。
JShellの動作:
JShellの最初の出力ステートメントの結果の例外を再現しようとしました-Java 9早期アクセスビルド151:
_jshell> class MyClass<N extends Number> {
...> N n = (N) (new Integer(8));
...> }
| Warning:
| unchecked cast
| required: N
| found: Java.lang.Integer
| N n = (N) (new Integer(8));
| ^--------------^
| created class MyClass
jshell> System.out.println(new MyClass<Long>().n);
8
jshell> System.out.println(new MyClass<Long>().n.getClass());
| Java.lang.ClassCastException thrown: Java.base/Java.lang.Integer cannot be cast to Java.base/Java.lang.Long
| at (#4:1)
_
しかし、JShellはIntelliJとまったく同じ結果を与えるようです。 System.out.println(new MyClass<Long>().n);
出力8-例外なし。
これはJava消去のために発生します。
Integer
はNumber
を拡張するため、コンパイラはN
へのキャストを受け入れます。実行時、N
はNumber
に置き換えられるため(消去により)、Integer
をn
内に格納しても問題はありません。
メソッドSystem.out.println
の引数はタイプObject
であるため、n
の値を出力しても問題はありません。
ただし、n
でメソッドを呼び出す場合、正しいメソッドが呼び出されるように、コンパイラーによって型チェックが追加されます。したがって、ClassCastException
になります。
例外と例外なしの両方が許容される動作です。基本的に、これは、キャストなしの次のようなものかどうかに関係なく、コンパイラーがステートメントを消去する方法にまで及びます。
_System.out.println(new MyClass().n);
System.out.println(new MyClass().n.getClass());
_
またはキャストでこのようなものに:
_System.out.println((Long)new MyClass().n);
System.out.println(((Long)new MyClass().n).getClass());
_
または1つのステートメントともう1つのステートメントの1つ。両方のバージョンが有効ですJavaコンパイルされるコード。問題は、コンパイラが1つのバージョンまたは他のバージョン、あるいはその両方にコンパイルできるかどうかです。
これは通常、型が型変数であるジェネリックコンテキストから何かを取得し、型変数が特定の型をとるコンテキストに返すときに発生するため、ここにキャストを挿入することは許容されます。たとえば、new MyClass<Long>().n
をキャストなしでLong
型の変数に代入するか、new MyClass<Long>().n
をキャストせずにLong
を期待する場所に渡すことができます。キャスト。どちらの場合も、コンパイラーがキャストを挿入する必要があることは明らかです。コンパイラーは、new MyClass<Long>().n
があるときに常にキャストを挿入することを決定できます。式がLong
を持っていると想定されているため、そうすることは間違いありません。
一方、これら2つのステートメントでキャストを使用しないことも可能です。これは、どちらの場合でも、式はObject
を使用できるコンテキストで使用されるため、キャストする必要がないためです。コンパイルしてタイプセーフにします。さらに、両方のステートメントで、値が実際にLong
である場合、キャストまたはキャストなしで動作に違いはありません。最初のステートメントでは、Object
を受け取る.println()
のバージョンに渡され、println
を受け取るLong
の特定のオーバーロードはありません。またはNumber
またはそのようなものなので、引数がLong
またはObject
と見なされるかどうかに関係なく、同じオーバーロードが選択されます。 2番目のステートメントの場合、.getClass()
はObject
によって提供されるため、左側のものがLong
またはObject
であるかどうかに関係なく使用できます。消去されたコードはキャストの有無にかかわらず有効であり、動作はキャストの有無にかかわらず同じです(実際にはLong
であると想定)、コンパイラはキャストアウトを最適化することを選択できます。
コンパイラは、1つのケースではキャストを持つこともできますが、特定の単純なケースでのみキャストを最適化し、複雑なケースでは分析を実行する必要がないためと考えられます。特定のコンパイラが特定のステートメント用に1つの形式または別の形式にコンパイルすることを決定した理由を詳しく説明する必要はありません。どちらも許可されており、どちらにしてもそれを当てにすべきではないからです。
これは、nを整数のオブジェクトとして既に定義しているため、longにキャストしないために発生しています
sysoutのMyClass
でIntegerを使用するか、
_System.out.println(new MyClass<Integer>().n);
_
またはn
をN n =(N)(new Long(8));
として定義します。