今回はJavaのオートボクシング機能についてです。
オートボクシング( autoboxing )とは
オートボクシングとは、基本データ型とそれに対応するラッパークラス間の変換を自動で行う機能です。
具体的には、
基本データ型 => ラッパークラスへ変換 : ボクシング
ラッパークラス => 基本データ型へ変換 : アンボクシング
と言います。
初期のJavaではオートボクシング機能が備わっておらず、int(基本データ型)とそのラッパークラスであるInteger間で型変換が必要でした。
// intからIntegerクラスに変換する Integer integerAA = new Integer(5); // Integerクラスからintに変換する int intB = integerAA.intValue();
しかし、オートボクシング機能が備わって型変換が不要になりました。
// int型の値をIntegerクラスに設定する(ボクシング) Integer integerAA = 5; // Integerクラスをint型の変数に設定する(アンボクシング) int intB = integerAA;
ただし、オートボクシングは「基本データ型<=>そのラッパークラス」の間に限られており、例えばlongとIntegerのような場合にはエラーになります。
long lngC = 5; Integer integerAA = lng5; // エラー:「型の不一致longからInteger型には変換できません」
オートボクシングの活用
このオートボクシング機能を活用することで、コレクションクラス(例: ArrayList)の扱いが非常に簡単になります。
理由としてはコレクションクラスでは基本データ型を直接扱えません。
その為、コレクションであるArrayListに追加する際には、intをInteger型に変換する必要があります。
// オートボクシングを考慮しない実装例 List<Integer> alstCast = new ArrayList<>(); alstCast.add(new Integer(100)); alstCast.add(new Integer(200)); int intCast1 = alstCast.get(0).intValue(); int intCast2 = alstCast.get(1).intValue();
上記のようなコードはエラーにはなりませんが、addする時もgetする時もコードが煩雑です。
一方で、オートボクシングを活用することでコードがシンプルになります。
// オートボクシングを活用した実装例 List<Integer> alistBox = new ArrayList<>(); alistBox.add(100); // ボクシング alistBox.add(200); int intBox1 = alistBox.get(0); // アンボクシング int intBox2 = alistBox.get(1);
比較に注意
比較の話をする前に、まずは四則演算での計算を見ていきます。
以下のようなintとIntegerを足し算する場合、Integerがintにアンボクシングされ、結果として300が表示されます。
int intA = 100; Integer integerBB = 200; System.out.println(intA + integerBB); // 結果:300
ちょっと面白いのは以下の例です。
Integer integerCC = 400; Integer integerDD = 300; System.out.println(integerCC + integerDD); // 結果:700
上記ではIntegerクラスしか使用していませんが、足し算するために自動的にint型へアンボクシングされ、結果が700となってます。
このオートボクシングにより、ラッパークラスは基本データ型のように扱えるように見えますが、比較する際は注意が必要です。
例えば以下の場合、Integerがアンボクシングされ、intの100と比較されます。
そのため、実行すると「同じ」と表示されます。
int intE = 100; Integer integerFF = 100; if (intE == integerFF) { System.out.println("同じ"); // こちらが出力される } else { System.out.println("違う"); }
では、IntegerとIntegerの比較の場合はどうなるでしょう?
ポイントはInteger同士の比較の場合はオートボクシングは行われず、オブジェクト同士の比較となるということです。
Integer integerGG = 100; Integer integerHH = 100; Integer integerII = new Integer(100); // 比較の危険性を説明する 例1 if (integerGG == integerHH) { System.out.println("同じ"); // こちらが出力される } else { System.out.println("違う"); } // 比較の危険性を説明する 例2 if (integerGG == integerII) { System.out.println("同じ"); } else { System.out.println("違う"); // こちらが出力される }
例1のintegerHHと例2のintegerIIの違いは「100」をそのまま代入(ボクシング)しているか、「new」を使って明示的にInteger型を生成しているかだけです。
どちらも同じ結果になりそうですが、 、例1は「同じ」、例2は「違う」と表示されます。
なぜこうなるのかを説明する前にもう一つ例を示します。
Integer integerJJ = 128; Integer integerKK = 128; // 比較の危険性を説明する 例3 if (integerJJ == integerKK) { System.out.println("同じ"); } else { System.out.println("違う"); // こちらが出力される }
例3は「new」は使っておらず、代入する値が128になっただけです。
となると、先ほどの結果から考えると「同じ」となりそうですが、出力結果は「違う」となります。
【なぜこうなるのか】
まず大前提として、Integer同士を「==」で比較した場合、「同じオブジェクトであるかどうか」を判定することとなります。
この前提で考えれば、どれも別々のオブジェクトとなりそうなので、全部「違う」という結果になりそうなものです。
しかし、Javaの仕様を確認してみると、Integerにはキャッシュ機能があり、「-128~127」の範囲のインスタンスが用意されています。
この範囲のInteger型の変数が宣言された場合、新たなインスタンスは生成せず、キャッシュされているインスタンスを使用します。
そのため、例1のように100同士だと「同じ」となります。
反対に例3のように128同士だと「違う」となってしまいます。
この話をさらにややこしくしているのが「new」の存在です。
「new」でInteger型の変数を宣言した場合は、必ず新たなインスタンスを生成するという仕様になっています。
そのため、例2のように「-128~127」の範囲であっても「違う」となってしまうのです。
ラッパークラスを比較する場合、オブジェクトを比較するequalsメソッドを使いましょう。
Integer integerJJ = 128; Integer integerKK = 128; if (integerJJ.equals(integerKK)) { System.out.println("同じ"); // こちらが出力される } else { System.out.println("違う"); }
頼りすぎは禁物
オートボクシングによってコレクションクラスやIntegerクラスなど、非常に使いやすいくなりました。
しかし、オートボクシングは便利ですが、パフォーマンスに影響を及ぼすことがあります。
以下の例では、計算時にオートボクシングが行われるため、性能に悪い影響を与えます。
Integer num = 1; for (int i = 0; i <= 1000000; i++) { num++; }
また、ラッパークラスはnullが入力できるため、安易に使用すると思わぬ箇所でNullPointerExceptionとなるかもしれません。
Integer num = null; // 様々な処理 int numInt = num; // NullpointerException
Integerなどラッパークラスを基本データ型ように使いたくなってしまいますが、ラッパークラスと基本データ型は別物であることを意識し、正しく使えるようにしましょう。