web-dev-qa-db-ja.com

Javaジェネリックス:なぜこの出力が可能なのですか?

私はこのクラスを持っています:

_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());
_
  1. 最初のSystem.out.println()ステートメントの出力:

    _8
    _
  2. 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番目の例外です。

51
Socke

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()は例外で失敗します。したがって、タイプLongoccursへのnのキャストは不可能です(IntegerLongにキャストすることはできません)。

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-例外なし。

39
Calculator

これはJava消去のために発生します。

IntegerNumberを拡張するため、コンパイラはNへのキャストを受け入れます。実行時、NNumberに置き換えられるため(消去により)、Integern内に格納しても問題はありません。

メソッドSystem.out.printlnの引数はタイプObjectであるため、nの値を出力しても問題はありません。

ただし、nでメソッドを呼び出す場合、正しいメソッドが呼び出されるように、コンパイラーによって型チェックが追加されます。したがって、ClassCastExceptionになります。

25
Raphaël

例外と例外なしの両方が許容される動作です。基本的に、これは、キャストなしの次のようなものかどうかに関係なく、コンパイラーがステートメントを消去する方法にまで及びます。

_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つの形式または別の形式にコンパイルすることを決定した理由を詳しく説明する必要はありません。どちらも許可されており、どちらにしてもそれを当てにすべきではないからです。

1
newacct

これは、nを整数のオブジェクトとして既に定義しているため、longにキャストしないために発生しています

sysoutのMyClassでIntegerを使用するか、

_System.out.println(new MyClass<Integer>().n);
_

またはnN n =(N)(new Long(8));として定義します。

0
Aman Goyal