オートボクシング機能について

今回は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などラッパークラスを基本データ型ように使いたくなってしまいますが、ラッパークラスと基本データ型は別物であることを意識し、正しく使えるようにしましょう。