基本データ型には参照型と同様に、明示的な変換と暗黙の型変換があります。boolean型を整数型や浮動小数点数型へ変換したり、逆の変換はできません。しかし、整数型と浮動小数点数型は、相互に変換できます。
ただし、それぞれの型が表現できる値の範囲、有効けた数に違いがありますから、それらを意識してプログラミングをする必要があります。
扱える値がより広いデータ型へ変換するような処理は、「広くする基本データ型変換(widening primitive)」といわれます。こういった変換は、明示的にキャストしなくても、暗黙のうちに型変換が自動適用されます。int型の値をlong型の変数へ代入する場合や、float型の値をdouble型の変数へ代入する場合です。もちろん、明示的にキャストしても構いません。
charは文字を表すデータ型ですがintが有効なところで暗黙の変換が適用されて使用できます。浮動小数点数は、同等か精度の高い浮動小数点変数に代入できます。浮動小数点数から整数へ暗黙の変換はできません。整数から浮動小数点数への暗黙の変換は可能です。
ただし暗黙の変換の中には、精度を失うものがあります。そのため、各データ型の有効けた数と、扱える値の範囲について、意識する必要があります。floatは32ビットで表現されますが、longは64ビットで表現されます。しかし、扱える値の範囲はfloatの方が広いので、次の例ではキャストが必要ありません。しかし、有効けた数の制限により精度が落ちてしまいます。
package sample18; public class Sample08 { public static void main(String[] args) { long longMax = Long.MAX_VALUE; float v = longMax; System.out.println(longMax); System.out.println(v); } }
このプログラムを実行すると、次のようになります。「9223372」より下位のけたについて、値が失われてしまっていることが分かります。
9223372036854775807 9.223372E18
扱える値がより狭いデータ型へ変換するような処理は、「狭くする基本データ型変換(narrowing primitive)」といわれます。大抵、キャストが必要になりますが、整数型の値については、キャストなしで小さな整数型に代入できる場合があります。値がlong型でないことと、指定された値を代入先の型が保持できる範囲内であることが条件です。
次の例では、x1、x2への代入は暗黙の型変換によりint型のリテラル値が自動的にshort型、byte型に変換されています。「16L」を指定している行は、リテラル値がlongであるため、有効にするとエラーとなります。「128」を指定している行は、byteが扱える値の範囲外の値を指定しているため、有効にするとエラーとなります。
package sample18; public class Sample09 { public static void main(String[] args) { short x1 = 16; byte x2 = 16; //short x3 = 16L; //byte x4 = 128; } }
このように、狭くする基本データ型変換ではエラーとなることがあります。「16L」を指定している行の処理のように、問題がない代入だということが分かっている場合は、実行したいところです。そんなときは、キャストを使います。
次の例では明示的なキャストしています。浮動小数点数値が整数にキャストされる場合は、小数点数部分は0に近い方に丸められて失われます。つまり、1.99は1となり、-1.99は-1となるということです。
また、代入するデータ型が扱える値の範囲を超えている場合は、int型とlong型については最大値(MAX_VALUE)や最小値(MIN_VALUE)になります。byte型、short型、char型については、浮動小数点数値はint型かlong型に変換されてから上位ビット分が取り除かれます。これにより、符号が変わってしまうこともあります。
この辺りは、データ型の内部表現について理解していないと分かりにくいでしょう。詳しく理解したい場合は、記事「EclipseでJavaの型を理解する」をご覧ください。
これらの処理を理解するために簡単なサンプルを実行してみましょう。x3の2進数表現を出力するために、IntegerクラスのtoBinaryStringメソッドを使っています。
package sample18; public class Sample10 { public static void main(String[] args) { double x1 = 1.99; System.out.println((long)x1); System.out.println((long)(-x1)); double x2 = (double)Long.MAX_VALUE; System.out.println(x2); System.out.println((long)x2); int x3 = (int)(-255.0); byte x4 = (byte)(-255.0); System.out.println(x3); System.out.println(Integer.toBinaryString(x3)); System.out.println(x4); } }
実行結果は、次のようになります。
1 -1 9.223372036854776E18 9223372036854775807 -255 11111111111111111111111100000001 1
読み取れる内容を1つずつ見ていきましょう。値が大きく変わっていることが分かります。
こういった特徴があることを理解したうえで、キャストを使うようにしましょう。
ここで、「数値の格上げ」について説明しておきます。すべての整数演算はintかlongの精度で行われます。このため、byte、shortは評価される前に常にintへ格上げされます。
浮動小数点数演算はfloatの精度で行われます。ただし、doubleが含まれる計算ではdoubleの精度で計算され、結果にはdoubleが使われます。
動作を確認するために次のようなプログラムを作成してみましょう。
package sample18; public class Sample11 { public static void main(String[] args) { byte b0 = 1; System.out.println("b0\t:"+b0); byte b1 = 2; System.out.println("b1\t:"+b1); //byte b2 = b0 + b1; // エラー byte b2 = (byte)(b0 + b1); System.out.println("b0+b1\t:"+b2); float f0 = 1.0f; System.out.println("f0\t:"+f0); //float f1 = 2.0; // エラー float f1 = (float)2.0; System.out.println("f1\t:"+f1); float f2 = f0 + f1; System.out.println("f0+f1\t:"+f2); double d0 = 5.0; System.out.println("d0\t:"+d0); double d1 = f0 + d0; System.out.println("f0+d0(double)\t:"+d1); //float f3 = f0 + d0; // エラー float f3 = (float)(f0 + d0); System.out.println("f0+d0(float)\t:"+f3); } }
byte型のb0とb1を加算すると、int型に格上げされてから計算がされてint型になります。このため、byte型のb2へ代入をするにはキャストが必要です。float型変数f1へdouble型のリテラル値2.0を直接代入できないため、キャストが必要です。
float型同士の加算では、float型の結果になるので、float型変数f2へ代入するに当たり、キャストは必要ありません。しかし、f0とd0の加算ではdouble型の結果となるので、float型変数f3へ代入するには、キャストが必要です。
実行結果は、次のようになります。
b0 :1 b1 :2 b0+b1 :3 f0 :1.0 f1 :2.0 f0+f1 :3.0 d0 :5.0 f0+d0(double) :6.0 f0+d0(float) :6.0
今回の計算では単純な計算しかしていないので、キャストにより精度が低くなったり、符号が変わるようなことはありません。
基本データ型の型変換では、広くする基本データ型変換や数値の格上げなどが暗黙のうちに適用されます。また、狭くする基本データ型変換ではキャストが必要です。こういったことについて知っていると、『なぜ「byte a=1; byte b=2; byte c=a+b;」でエラーが発生するのか』が分かるはずです。
キャストを使う際は、計算対象としている数値の範囲や精度をきちんと意識する必要があることを説明しました。誤用すると、求めたいものとは全然違う計算結果となってしまいますから、気を付けましょう。
最後に、文字列変換について説明します。文字列とほかの値を「+」で結合すると、この「+」は「文字列結合演算子」となるので、ほかの値は暗黙の型変換で文字列となり、結果は文字列となります。基本データ型にしろ、参照型にしろ、いずれにせよ文字列へ変換されます。「null参照」が文字列へ変換されると、「null」という文字列になります。
基本データ型の文字列変換は決まっていますが、参照型の場合はObject型のtoStringメソッドが使われるので、オーバーライドによって動作を変更できます。
確認のために、次のようなプログラムを作成してみましょう。最初に「abc」という文字列をjava.lang.String型の変数sへ代入し、「+」でいろいろな値と文字列結合します。整数値1、不動小数点数2.0、Object型のインスタンス、toStringメソッドをオーバーライドしたObject型のインスタンス、null参照を次々と結合しています。
package sample18; public class Sample12 { public static void main(String[] args) { String s = "abc"; s = s + ", " + 1; s = s + ", " + 2.0; Object o = new Object(); s = s + ", " + o; o = new Object() { public String toString() { return "HelloObject"; } }; s = s + ", " + o; s = s + ", " + null; System.out.println(s); } }
実行結果は、次のようになります。想像したとおりになったのではないでしょうか。
abc, 1, 2.0, java.lang.Object@ca0b6, HelloObject, null
文字列と「+」を使ってほかの値を結合すると、ほかの値について文字列変換が暗黙のうちに適用されます。この文法のおかげで、いちいちString型へ変換するためのメソッドを呼び出さなくても済むので便利です。
また「null参照」では、文字列変換されると「null」という文字列になるので、表示したくない場合には、きちんと判定して表示しないようにする必要があります。
今回はオーバーロードも含めつつ型変換について説明しましたが、いかがだったでしょうか。細かい話が多くて「小難しい」と感じたのではないでしょうか。厳密な数値計算をする必要に迫られないと、こういった話を理解する気が起きないものです。「コンピュータで数値計算をするときは、気を付けることが多い」という点だけは理解しておいてください。
以上の型変換に関係する文法事項をよく理解して、正しい数値計算ができるようになりましょう。次回は「ジェネリックス」について解説をする予定です。今回作ったサンプルのソースコードはこちらからダウンロードできます。
小山博史(こやま ひろし)
情報家電、コンピュータと教育の研究に従事する傍ら、オープンソースソフトウェア、Java技術の普及のための活動を行っている。長野県の地域コミュニティである、SSS(G)やbugs(J)の活動へも参加している。
著書に「基礎Java」(インプレス)、共著に「Javaコレクションフレームワーク」(ソフトバンククリエイティブ)、そのほかに雑誌執筆多数。
Copyright © ITmedia, Inc. All Rights Reserved.