最近Javaの基本を改めて勉強し直しているのですが、プリミティブ型、特にint型の扱いで「え、そうだったの?!」と驚いたことがたくさんあったので、自分の学びのアウトプットとしてシェアしたいと思います。
指摘や気づき等ありましたら気軽にコメントしていただけるとありがたいです!
int型の限界を知らないと起こる悲劇(オーバーフロー)
Javaで一番よく使う整数型、int型。私も「ただの数字の入れ物」くらいに思っていたのですが、このint型には「約-21億から+21億」というきっちり決まった限界があるんです。
コードに直接、範囲外の値を代入しようとすればコンパイルエラーになりますが、計算の結果が範囲外になった場合は、なんとエラーにならないんです!
int myMinIntValue = -2147483648; // int型の最小値
System.out.print("Busted Max Value = " + (myMinIntValue - 1));
出力結果: Busted Max Value = 2147483647
最小値から1を引いたのに、なぜか最大値にジャンプ!これがアンダーフローです。エラーが出ないまま、意図とは全く違う値が代入されてしまうなんて、恐ろしいですよね。「int型には限界がある」ということを、常に意識しないといけないと学びました。
大きな数字の可読性を上げる超便利ワザ
'2147483647'のような長い数字を見たとき、「桁数、いくつだっけ?」って一瞬戸惑いませんか?
Javaでは、数値リテラル(コードに直接書いた値)で「_ (アンダースコア)」を桁区切りとして使えるんです!
// どっちが読みやすい?圧倒的に下!
int myMaxIntReadable = 2147483647;
int myMaxIntReadable = 2_147_483_647;
これはコンパイル時に無視されるので、実行には全く影響がありません。コードの可読性を上げるために積極的に使っていきたいなと思いました。
異なる型で計算すると「勝手に昇格」する問題
「型」が違う変数同士を計算するとき、Javaは気を利かせて「より安全な大きな型」に合わせて計算してくれます。これを型昇格と呼びます。
例えば、byte型に入っている変数を計算に使うと...
byte a = 10;
byte test = (a / 2); // コンパイルエラー!
10 / 2 = 5で、byte型に収まるはずなのに、エラーになるんです。
なぜなら、aはbyte型ですが、計算式の2はデフォルトでint型と見なされます。byteとintで計算すると、結果は「より大きな」int型になります。
そのint型の結果を、byte型の変数に入れようとするため、「サイズオーバーの可能性があるよ!」とJavaがストップをかけてくれるわけです。
解決策は、結果を明示的にキャスト(型変換)してあげることです。
byte test = (byte) (a / 2); // (byte)で明示的に変換すればOK
金銭や精密な計算は「浮動小数点型」ではダメ!
「小数点を使いたいならfloatかdoubleでしょ?」と考えていたんですが、お金の計算など、絶対に正確な計算が必要な箇所でこれらを使うのは厳禁だと知りました。
floatやdoubleは、コンピュータの仕組み上、わずかな誤差を含んでしまう性質があるからです。
では、どうするか?
答えは、BigDecimalクラスを使うことです!
import java.math.BigDecimal;
// 金額や率など、精密さが求められる値はこれで扱う!
BigDecimal price = new BigDecimal("100.50");
// 計算には専用メソッドを使います
BigDecimal tax = price.multiply(new BigDecimal("0.10"));
正確さを求めるならこれ一択です。
一文字の扱い:char型とUnicode
文字を1文字だけ格納したいときは、char型を使います。
特徴は、値をシングルクオート(')で囲むことです。char型は、内部的にはUnicodeという文字コードの数字を保持しています。
char myChar = 'D';
char anotherChar = '\u0044'; // 'D'のUnicode表現
複合代入演算子の「暗黙の型キャスト」に注意
最後に、私が一番驚いた落とし穴です。
+=や-=のような複合代入演算子を使うとき、意図しない型キャストが裏側で行われているんです。
int x = 10;
x -= 5.5; // コンパイルエラーにならない!
System.out.println(x); // 出力結果は「4」
なぜ4.5ではないのか?
実は、x -= 5.5という記述は、Javaによって暗黙的に型キャストするコードに展開されます。
x=(int)(x−5.5);
つまり、x - 5.5で計算された4.5というdouble型の値が、自動的に(int)でキャストされ、小数点以下が切り捨てられて4になるんです。
複合代入演算子は便利ですが、裏側で勝手に型変換が行われていることを知っておかないと、思わぬバグを生みそうなので、気をつけたいと思います!
まとめと次の目標
今回、Javaのプリミティブ型について深く学ぶことで、単なる「変数」というより「データを安全に扱うための仕組み」なんだという視点に変わりました。
int型の限界(オーバーフロー)に注意し、long型なども視野に入れる。
正確な計算が必要な場所では、手間でもBigDecimalを使う。
複合代入演算子を使う際は、暗黙の型キャストが行われることを忘れない。
今後も、基本知識をしっかり固めて、よりバグの少ない、人に優しいコードを書いていきたいと思います!