web-dev-qa-db-ja.com

なぜJavaが配列からコピーされた場合、最終変数に明示的にキャストする必要があるのですか?

次のコードから開始...

byte foo = 1;
byte fooFoo = foo + foo;

このコードをコンパイルしようとすると、次のエラーが表示されます...

エラー:(5、27)Java:互換性のない型:intからbyteへの損失の可能性のある変換

...ただし、fooが最終の場合...

final byte foo = 1;
final byte fooFoo = foo + foo;

ファイルは正常にコンパイルされます。

次のコードに移ります...

final byte[] fooArray = new byte[1];
fooArray[0] = 1;

final byte foo = fooArray[0];
fooArray[0] = 127;

System.out.println("foo is: " + foo);

...印刷します

foo is: 1

...結構です。値は最終変数にコピーされ、変更できなくなります。配列の値を操作しても、fooの値は変更されません(予想どおり...)。

次の場合、キャストが必要なのはなぜですか?

final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
final byte fooFoo = foo + foo;

これは、この質問の2番目の例とどう違うのですか?コンパイラから次のエラーが表示されるのはなぜですか?

エラー:(5、27)Java:互換性のない型:intからbyteへの損失の可能性のある変換

どうしてこれが起こりますか?

57
Koray Tugay

JLS( §5.2 )には、定数式を使用した代入変換のための特別なルールがあります。

また、式がタイプbyteshortchar、またはintの定数式( §15.28 )の場合:

  • 変数の型がbyteshort、またはcharであり、定数式の値が変数の型で表現可能な場合、絞り込みプリミティブ変換を使用できます。

上記のリンクをたどると、これらはconstant expressionの定義に表示されます。

  • プリミティブ型のリテラルとString型のリテラル
  • 加法演算子+および-
  • 定数変数を参照する単純な名前( §6.5.6.1 )( §4.12.4 )。

上記の2番目のリンクをたどると、

プリミティブ型またはタイプString、つまりfinalであり、コンパイル時の定数式( §15.28 )で初期化された変数は、定数変数と呼ばれます。

foo + fooは、fooFoo定数変数である場合にのみfooに割り当てることができます。それをあなたのケースに適用するには:

  • byte foo = 1;は、finalではないため、定数変数を定義しません

  • final byte foo = 1;doesは、定数変数を定義します。これは、finalであり、定数式(プリミティブリテラル)。

  • final byte foo = fooArray[0];は、定数式で初期化されないため、定数変数を定義しません

fooFoo自体がfinalであるかどうかは重要ではありません。

45
shmosel

値1は1バイトにうまく収まります。 1 + 1も同様です。そして、変数がfinalの場合、コンパイラーは 定数の折りたたみ を実行できます。 (言い換えると、コンパイラは、その+操作を実行するときにfooを使用しませんが、「raw」1値)

しかし、変数が最終的なものではない場合、変換とプロモーションに関するすべての興味深いルールが始まります( here を参照してください;プリミティブ変換の拡大についてはセクション5.12を読みたいです)。

2番目の部分では、配列をfinalにすることで、任意のフィールドをchangeできます。再び。一定の折りたたみはできません。そのため、「拡大」操作が再び開始されます。

17
GhostCat

実際、バイトコードからわかるように、コンパイラはfinalを使用して定数畳み込みで実行します。

    byte f = 1;
    // because compiler still use variable 'f', so `f + f` will 
    // be promoted to int, so we need cast
    byte ff = (byte) (f + f);
    final byte s = 3;
    // here compiler will directly compute the result and it know
    // 3 + 3 = 6 is a byte, so no need cast
    byte ss = s + s;
    //----------------------
    L0
    LINENUMBER 12 L0
    ICONST_1 // set variable to 1
    ISTORE 1 // store variable 'f'
    L1
    LINENUMBER 13 L1
    ILOAD 1 // use variable 'f'
    ILOAD 1
    IADD
    I2B        
    ISTORE 2 // store 'ff'
    L2

    LINENUMBER 14 L2
    ICONST_3 // set variable to 3
    ISTORE 3 // store 's'
    L3
    LINENUMBER 15 L3
    BIPUSH 6 // compiler just compute the result '6' and set directly
    ISTORE 4 // store 'ss'

そして、最終バイトを127に変更すると、文句を言うでしょう:

    final byte s = 127;
    byte ss = s + s;

その場合、コンパイラーは結果を計算し、限界を超えていることを知っているので、互換性がないと文句を言います。

もっと:

そして、 here は、文字列を使用した定数の折りたたみに関する別の質問です。

7
Tony