1. ホーム
  2. Java

強制型変換について

2022-02-07 09:31:54
まずは例題から。
char c=1;
c=c+3;

/*
error: incompatible types: possible lossy conversion from int to char
c=c+3;
   ^
*/
char c=1;
c=1+3;

<スパン <スパン コンパイル時にエラーが発生しました。

char c=1; //0: iconst_1 puts constant 1 on top of the stack
// //1: istore_1 stores the value 1 at the top of the stack into local variable 1, i.e. c
c=1+3; ///2: iconst_4 puts constant 4 on the top of the stack
// // 3: istore_1 stores the value 4 at the top of the stack into the local variable 1, i.e. c

<スパン 二項演算子 "+"記号のオペランドは、合計される型が同じでなければならず、その後、左変数に代入され、最終的な型は左変数によって決められます。型が一致しない場合、Javaは次の方向に、デフォルト変換(または自動型昇格)を試行します。


<スパン (a) Javaでは、型の範囲が小から大になると自動変換を行い、逆に大から小になると精度が落ちる可能性があるため、強い変換(キャスト)を必要とします。
この例では、変数 "c" が char 型、定数 "3" が int 型として扱われ、加算時には char が自動的に int に変換され、これは正常ですが、代入時には int を char に変換する必要があり、精度が落ちてエラーが報告されています。
強力な変換を行うには、変換先の型を括弧で囲み、その後に変換する変数名を指定する必要があります(例:c=(char)(c+3))。
ここで、以下のコードがエラーを報告しないことを不思議に思うのは簡単です。

char c=1;
c=65535+1;

<スパン <スパン 2行目では、定数"1"と"3"はどちらもint型であり、代入もcharに変換されませんが、ここでは強い変換がないのは正常です、なぜですか? <スパン
<スパン 実はこれはコンパイラの判断で、どちらも直接計算すれば結果が得られる定数なので、コンパイラは結果の値が左の型の範囲内かどうかを判断し、範囲内なら値を代入し、そうでなければ同様にエラーを報告するのです。
この情報はクラスファイルの中にあるので、JDK独自のjavapツールを使って見ます(javap -c Test)。
char c=1;
c+=65535;

<スパン <スパン (a) コンパイラが "1+3" に対して演算を行い、結果 "4" を直接得たが、これは char 型の範囲内なので、そのまま代入できる、c=c+3 に変更すると、型によって決まる特定の値を取得できず、エラーとなる。
ちょっとした検証はここでできます。
char c=1;
c+=65535;
c=(char)(c+65535);
/* 0: iconst_1      
       1: istore_1

       // The following is c+=65535;
       2: iload_1       
       3: ldc #2 // int 65535
       5: iadd          
       6: i2c // strong conversion: int to char
       7: istore_1

       // the following: c=(char)(c+65535);
       8: iload_1       
       9: ldc #2 // int 65535
      11: iadd          
      12: i2c // strong turn  
      13: istore_1
*/

<スパン <スパン をコンパイルすると、charは2バイト長なので、範囲は0〜2^16-1すなわち0〜65535で、定数和ではありますが、結果がcharの範囲を超えてしまうとint型として扱われ、その後に代入がまだ強回転する必要があるため、同じエラーとなりました。
(p.s. JDK1.6以上ではこの方法でテストしています)
ここで2つ目の難問が発生する。
char c=1;
c+=65535;

<スパン 上記のコードが正しくコンパイルされ、実行されるのはなぜですか?
続けてjavapツール(JDK 1.8)を使って見てみましょう。
char c=1;
c+=65535;
c=(char)(c+65535);

/* 0: iconst_1      
       1: istore_1

       // The following is c+=65535;
       2: iload_1       
       3: ldc #2 // int 65535
       5: iadd          
       6: i2c // strong conversion: int to char
       7: istore_1

       // the following: c=(char)(c+65535);
       8: iload_1       
       9: ldc #2 // int 65535
      11: iadd          
      12: i2c // strong turn  
      13: istore_1
*/

複合代入演算子ステートメント: "c+=65535;" は "c=(char)(c+65535);" と同等なので、コンパイルしてエラーなく実行できることがわかりますが、特に値を2進コードに変換することで望む結果とは異なることがあります。
最後に、char, byte, shortは見かけ上の型であり、下部でintに変換されるため、"c=c+c;"という記述も強変換してコンパイルを通さなければならないのです。