Java | Java 標準ライブラリ:auto-boxing

Java Java
スポンサーリンク

auto-boxing は「基本型 ↔ ラッパー型の自動変換」

まず前提として、Java には

  • 基本型(プリミティブ型)
    int, long, double, boolean, char など
  • ラッパークラス(オブジェクトとしての箱)
    Integer, Long, Double, Boolean, Character など

のペアが存在します。

int     i  = 10;
Integer ii = Integer.valueOf(10);  // ラッパークラス
Java

もともと Java では、「基本型」と「ラッパークラス」は別物で、
相互に変換するときは自分で書いていました。

auto-boxing(オートボクシング)は、

「基本型 ↔ ラッパークラスの変換を、コンパイラが自動でやってくれる仕組み」

です。

  • 基本型 → ラッパークラス への自動変換 … boxing(ボクシング)
  • ラッパークラス → 基本型 への自動変換 … unboxing(アンボクシング)

この2つをまとめて「auto-boxing/unboxing」と呼ぶことが多いです。


auto-boxing の基本的な動き

代入で起きる auto-boxing / auto-unboxing

単純な代入で見てみます。

int i = 10;

// 基本型 → ラッパー型(auto-boxing)
Integer obj = i;

// ラッパー型 → 基本型(auto-unboxing)
int j = obj;
Java

見た目は「代入しているだけ」ですが、
コンパイラは内部的に、だいたいこんなコードに変換します。

Integer obj = Integer.valueOf(i);  // boxing
int j = obj.intValue();            // unboxing
Java

つまり、「ラッパークラスの valueOf, xxxValue メソッド呼び出し」を
コンパイラが勝手に差し込んでくれている、ということです。

コレクションとの組み合わせでよく使われる

List<int> のように、基本型を直接コレクションに入れることはできません。
List<Integer> のようにラッパークラスを使います。

import java.util.*;

List<Integer> list = new ArrayList<>();

// ここで auto-boxing が起きている
list.add(10);    // int → Integer に自動変換

// ここで auto-unboxing が起きている
int x = list.get(0);  // Integer → int に自動変換
Java

昔の Java なら、こう書かないといけませんでした。

list.add(Integer.valueOf(10));
int x = list.get(0).intValue();
Java

auto-boxing のおかげで、
「基本型を扱っているつもり」で自然なコードが書けるようになっています。


どこで auto-boxing が起きるのかを肌で感じる

メソッド呼び出しの引数での auto-boxing

ラッパークラスを引数に取るメソッドを呼ぶときも、自動変換が走ります。

void printInteger(Integer x) {
    System.out.println("x = " + x);
}

int i = 10;
printInteger(i);  // int だが、自動で Integer に変換されて渡される
Java

コンパイラは、だいたいこう変換します。

printInteger(Integer.valueOf(i));
Java

反対に、ラッパー型を受け取るメソッドが基本型を返す場合も unboxing されます。

Integer getInteger() {
    return 10;  // ここで auto-boxing(10 → Integer)
}

int x = getInteger();  // ここで auto-unboxing(Integer → int)
Java

算術演算でも unboxing → 計算 → boxing が起きる

ラッパークラス同士を + したりすると、
内部で unboxing → 計算 → boxing が行われます。

Integer a = 10;
Integer b = 20;

Integer c = a + b;  // 実際には a.intValue() + b.intValue() → 結果を Integer に boxing
Java

コンパイラが変換すると、だいたいこんなイメージです。

Integer c = Integer.valueOf(a.intValue() + b.intValue());
Java

見た目は「オブジェクト同士を +」しているようですが、
実際には基本型として計算しています。


auto-boxing が生む「落とし穴」1:null の unboxing による NPE

典型的な危険パターン

いちばんよくある事故がこれです。

Integer n = null;
int x = n;   // ここで NullPointerException
Java

ここで起きているのは:

int x = n;
x = n.intValue(); に変換される
n が null なので null.intValue() 呼び出し
→ NullPointerException

という流れです。

ラッパークラスは「null を持てる」ので、
DB や外部入力から Integer として値を受け取るときなど、
null が紛れ込むのは珍しくありません。

そこから、何も考えずに基本型に代入すると、
この「null アンボクシング NPE」を食らいます。

対策の考え方

ラッパークラスを基本型に落とすときは、
「null の可能性があるか?」を必ず意識する必要があります。

例えば:

Integer n = ...;  // null かもしれない

int x = (n != null) ? n : 0;   // null なら 0 にする、などのルールを決める
Java

あるいは Optional と組み合わせて:

Integer n = ...;
int x = Optional.ofNullable(n).orElse(0);
Java

など、「null のときどうするか」を明示的に決めてから unboxing する方が安全です。


auto-boxing の「落とし穴」2:== 比較での誤解

ラッパークラスの == は「参照比較」

Integer, Long などのラッパークラスはオブジェクトなので、
== で比較すると「同じインスタンスかどうか」を見ます。

Integer a = 100;
Integer b = 100;
System.out.println(a == b);   // true になることが多い

Integer x = 200;
Integer y = 200;
System.out.println(x == y);   // false になることが多い
Java

この挙動の理由は、「小さい整数をキャッシュする」仕様などですが、
詳細を覚える必要はありません。

ここで重要なのは、

ラッパークラス同士の == 比較に頼るべきではない

ということです。

値として比較したいなら、

a.equals(b);      // 値として等しいか
Java

または、ぐっとシンプルに unboxing して

int ai = a;
int bi = b;
ai == bi;         // int 比較
Java

のようにするほうが安全です。

auto-boxing があるせいで「なんとなく == でもイケそう」に見えてしまうので、
ここは意識して「equals または unboxing」と決めてしまうと迷いません。


auto-boxing の「落とし穴」3:パフォーマンス(大量の boxing)

ループ内での無駄なオブジェクト生成

auto-boxing は便利ですが、
裏では「オブジェクトの new やキャッシュ取得」が行われています。

巨大なループの中でボクシング/アンボクシングを繰り返すと、
その分だけオブジェクト生成や GC のコストがかかります。

例えば:

long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    Integer x = i;   // ここで毎回 auto-boxing(Integer オブジェクト生成)
    sum += x;        // ここで auto-unboxing
}
Java

初心者のうちはそこまで神経質になる必要はありませんが、
「数値計算はなるべく基本型で完結させる」「コレクションに入れるところだけラッパー」という意識を持っておくと、
いざパフォーマンスを意識するときのベースになります。


どんなときに auto-boxing を“頼ってよいか”を整理する

素直に頼っていい場面

次のような場面では、auto-boxing に素直に頼って構いません。

コレクションへの追加・取得

List<Integer> list = new ArrayList<>();
list.add(10);          // auto-boxing
int v = list.get(0);   // auto-unboxing
Java

メソッド呼び出しの引数/戻り値

void print(Integer x) { ... }

print(5);   // int → Integer に自動変換
Java

軽い計算に混ざるラッパー型

Integer a = 1;
int b = 2;
int c = a + b;   // a が unboxing されてから計算
Java

ここは、コードの読みやすさを優先して大丈夫です。

気を付けるべき場面

一方で、次のようなところでは意識が必要です。

ラッパー型から基本型への代入(null の可能性)

Integer n = ...;  // null かも?
int x = n;        // ここで NPE の危険
Java

ラッパークラス同士の比較

Integer a = 100;
Integer b = 100;
boolean same = (a == b);  // 安全ではない
Java

大量ループでの無意識な boxing

List<Integer> list = ...;
long sum = 0;
for (Integer n : list) {
    sum += n;  // 毎回 unboxing(必要なら OK、ただ意識はしておく)
}
Java

ここで「本当にラッパー型が必要なのか?」「null が本当に必要なのか?」を
一度立ち止まって考えることが、きれいな設計への第一歩になります。


まとめ:auto-boxing をどう意識してコードを書くか

auto-boxing を一言で言い換えると、

「基本型とラッパークラスの境界を、コンパイラが“それっぽく”橋渡ししてくれる機能」

です。

そのおかげで、コレクションや API を使うときのコードはだいぶ書きやすくなりましたが、
同時に

  • ラッパー型の null → unboxing で NPE
  • ラッパー型同士の == 比較の誤用
  • 大量 boxing によるパフォーマンス低下

といった「見えないところで動いている変換」による罠も生まれています。

初心者としてはまず、

  • 「ここで auto-boxing / unboxing が起きているな」と意識できるようになる
  • ラッパー型から基本型に落とすときは、null の可能性を必ず考える
  • 値の比較は equals か unboxing した上で == を使う

このあたりを意識して書ければ十分です。

タイトルとURLをコピーしました