本ページはプロモーションが含まれています

Java入門

アンボクシングとは?Javaで起きるバグと回避策

Unboxing(起きるバグと回避策)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

Javaのアンボクシングとは、Integer や Double などのラッパークラスから、int・double などのプリミティブ型へ自動変換する機能です。

逆方向の変換(プリミティブ型 → ラッパークラス)は「オートボクシング」と呼び、どちらもJava 5から導入されました。

この記事でわかること

  • アンボクシング・オートボクシングの仕組み
  • NullPointerExceptionや==比較バグを防ぐ3つの注意点
  • ループ処理で性能低下を避ける書き方

仕組みを理解しないまま使うと、NullPointerException の発生やパフォーマンス低下など思わぬバグを引き起こします。

特に多いのが、null の入った Integer を int に代入した瞬間に落ちるケースです。コンパイルは通るため、実行するまで気づけません。

オートボクシングとは?自動型変換

Javaのオートボクシングは、プリミティブ型を、対応するラッパークラスのオブジェクトに自動で変換してくれる機能です。

オートボクシングのおかげで、型変換を手で書かなくても簡潔なコードになります。

たとえば List には本来オブジェクトしか入りません。それでも int の値を直接追加できるのは、裏でオートボクシングが働いているためです。

ボクシングとは?プリミティブ型とラッパークラスの関係

オートボクシングを理解するには、まず「プリミティブ型」と「ラッパークラス」の関係を押さえておきましょう。

  • プリミティブ型: int、double、boolean のように、数値や真偽値などの「値」そのものを直接格納する基本的なデータ型です。ローカル変数ならスタック領域に直接値が保存されるため、高速にアクセスできます。
  • ラッパークラス: Integer、Double、Boolean のように、各プリミティブ型に対応したクラスです。プリミティブ型の値をオブジェクトとして扱えるようにラップしたもので、ヒープ領域にオブジェクトとして生成されます。
プリミティブ型ラッパークラス
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

通常、プリミティブ型の値をラッパークラスのオブジェクトに変換する操作を「ボクシング」と呼びます。

java
// 手動でのボクシング
int primitiveInt = 100;
Integer wrapperInt = Integer.valueOf(primitiveInt);

Integer.valueOf() を毎回書く変換を、コンパイラが自動で行ってくれるのがオートボクシングです。

java
// オートボクシング
Integer wrapperInt = 100; // intリテラルからIntegerに自動変換

オートボクシング導入の背景

オートボクシングは、Java 5(2004年〜)から導入されました。

導入の大きな理由は、同じくJava 5で導入されたジェネリクスの存在です。

ジェネリクスは、List<T> や Map<K, V> のように、コレクションに格納する要素の型をコンパイル時に指定できる画期的な機能でした。

あわせて読む

しかし、ジェネリクスの型パラメータ T には、Integer のようなクラス(参照型)しか指定できず、int のようなプリミティブ型は指定できません。

もしオートボクシングがなければ、List に数値を追加するたびに、次のような煩雑なコードを書く必要がありました。

java
List integerList = new ArrayList();
integerList.add(new Integer(10)); // 手動でボクシング

変換を手で書き続けると、コードが読みにくくなり、書く手間も増えます。

ジェネリクスを手軽に使えるようにするため、Java 5でオートボクシングが一緒に導入されました。

自動変換が起こる場面

オートボクシングは、主に以下のような場面で活躍します。

1. ジェネリクスを使ったコレクションへの要素追加

最も代表的な例です。int 型の値を、Integer 型を扱うリストに直接追加できます。

java
List<Integer> numbers = new ArrayList<>();

// 本来は Integer オブジェクトしか追加できない
// ここでオートボクシングが働き、intの100が Integer.valueOf(100) のように変換される
numbers.add(100);
numbers.add(200);

System.out.println(numbers); // [100, 200]

2. ラッパークラス型の変数へのプリミティブ値の代入

Integer 型の変数に int 型の値を直接代入する際にも、オートボクシングが発生します。

java
// int型の値 50 が、Integer型の変数に代入される
Integer number = 50;

3. メソッドの引数

ラッパークラスを引数に取るメソッドに、プリミティブ型の値を渡す場合も同様です。

java
public void printInteger(Integer num) {
    System.out.println("値は " + num + " です。");
}

// int型の 300 を引数に渡しているが、内部でIntegerに変換される
printInteger(300);

アンボクシングとは?逆操作の仕組み

アンボクシングは、オートボクシングとは逆の操作です。

ラッパークラスのオブジェクトから、対応するプリミティブ型の値を取り出す変換を自動的に行ってくれます。

箱(Box)に詰められた(ボクシングされた)値を取り出すイメージから、アンボクシング(Unboxing)と呼ばれています。

アンボクシングが起こるタイミング

アンボクシングは、主に次のような状況で発生します。

1. プリミティブ型の変数へのラッパークラスオブジェクトの代入

int 型の変数に Integer 型のオブジェクトを代入すると、自動で int の値が取り出されます。

java
Integer wrapperInt = Integer.valueOf(100);

// wrapperIntオブジェクトからint値の100が取り出されて代入される
int primitiveInt = wrapperInt;

2. 算術演算

ラッパークラスのオブジェクトを、+、-、*、/ などの算術演算子で計算しようとすると、計算の前にアンボクシングが発生します。

オブジェクト同士は直接計算できないため、いったんプリミティブ型に戻してから計算します。

java
Integer a = 200;
Integer b = 300;

// aとbがそれぞれint型にアンボクシングされてから足し算が実行される
int result = a + b;

System.out.println(result); // 500

nullのときの注意点と例外(NullPointerException)

アンボクシングで最も注意すべき点が、null の扱いです。

ラッパークラスはオブジェクトなので、null を代入できます。

しかし、null が代入されたラッパークラス変数をアンボクシングしようとすると、NullPointerException が発生します。

null という「空っぽの箱」から値を取り出そうとして失敗する、というイメージです。

実際に私も新人の頃、DBから取得した Integer 型の値が null だったことに気づかず、アンボクシング時に NullPointerException が発生してレビューで指摘された経験があります。

次のコードは、コンパイルは通るのに実行時に落ちます。

java
Integer number = null;

// numberはnullなので、値を取り出そうとするとNullPointerExceptionが発生!
int value = number; // ここで例外が発生

コンパイルエラーにならず、実行して初めて落ちるため、原因に気づきにくいバグです。

アンボクシングの裏側で起きている処理を図にすると、次のようになります。

int value = number; の裏側(タップで切り替え)

  1. int value = number;Integer を int に代入(計算に使う場合も同じ)
  2. コンパイラが number.intValue() を自動で挿入コード上には見えない呼び出し

対策:代入の前に if (number != null) で確認する

null が入る可能性のあるラッパークラスを扱う際は、アンボクシングされる前に必ず null チェックを行う習慣をつけましょう。

java
Integer number = null;
int value = 0;

if (number != null) {
    // nullでないことを確認してからアンボクシング
    value = number;
}

System.out.println(value); // 0
ボクシングとアンボクシングの往復
int
プリミティブ型(5)
オートボクシング →
← アンボクシング
Integer
ラッパークラス(オブジェクト)
Integer が null のままアンボクシング → NullPointerException

オートボクシング・アンボクシングの一連の流れ(コード例)

一連のコードの中で、どこで2つの変換が起きているか見てみましょう。

java
// 1. ListをInteger型で準備
List<Integer> scores = new ArrayList<>();

// 2. int型の値を追加(オートボクシング)
scores.add(88); // int -> Integer

// 3. Integerオブジェクトを取り出し、int型変数へ代入(アンボクシング)
int myScore = scores.get(0); // Integer -> int

// 4. 算術演算で利用(アンボクシング)
int bonusScore = myScore + 10; // myScoreは既にintなので不要
Integer totalScore = scores.get(0) + 20; // scores.get(0)がアンボクシングされる

System.out.println("合計点: " + totalScore);

変換はすべてコンパイラが自動で挟むため、コード上には変換処理が1行も現れません。

オートボクシングの落とし穴とパフォーマンスへの影響

便利なオートボクシングですが、自動変換の手軽さゆえに注意すべき「落とし穴」が3つあります。

仕組みを知らずに多用すると、バグや処理速度の低下につながります。

意図しないオブジェクト生成によるメモリコスト

オートボクシングは、プリミティブ型をラッパークラスのオブジェクトに変換する処理です。

つまり、オートボクシングが発生するたびに、新しいオブジェクトがメモリ(ヒープ領域)上に生成される可能性があります。

ループの中で無意識にオートボクシングを起こすと、短時間で大量のオブジェクトが作られ、メモリを圧迫します。

不要になったオブジェクトを回収するため、ガベージコレクション(GC)も頻繁に動きます。その結果、アプリケーション全体の処理速度が落ちます。

equalsと==の違いによるバグ

なぜ同じ数値なのに == が false になるのかは、図で動かすJVMメモリ|スタックとヒープ・参照・GCの関係を見ると一目で分かります。== は矢印の先が同じ箱かを比べ、equals は箱の中身を比べている、という図です。

Javaの == 演算子は、比較する2つの変数が同じオブジェクト(メモリ上の同じアドレス)を参照しているかを判定します。

一方、.equals() メソッドは、2つのオブジェクトが持つ値が等しいかを判定します。

== と .equals() の違いは、オートボクシングと組み合わさると次のようなバグを生みます。

java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false

System.out.println(c.equals(d)); // true

同じ書き方なのに、100 は true、200 は false になるのはなぜでしょうか。

原因は、Integer クラスが内部で -128 から 127 までの値をキャッシュしていることです。

この範囲はJLS(Java言語仕様)で保証されています。上限はJVMによっては java.lang.Integer.IntegerCache.high で変更できます。

この範囲の値がオートボクシングされる際は、新しいオブジェクトを作らず、キャッシュされた既存のオブジェクトを再利用します。

そのため、100 を代入した a と b は同じオブジェクトを参照しており == の結果が true になります。

しかし、キャッシュ範囲外の200を代入した c と d は、それぞれ新しいオブジェクトとして生成されるため、== の結果は false になるのです。

値を選ぶと == の結果が変わる

Integer a = 100;Integer b = 100;

キャッシュ範囲内(-128〜127)
変数
a → #1
b →
ヒープ上の Integer
#1100
#2100
a == b →
a.equals(b) → true

範囲内の値はキャッシュ済みの同じオブジェクトが使い回され、範囲外では別々のオブジェクトが作られます(上限は標準設定の場合)。

値の範囲によって == の結果が変わるため、ラッパークラス同士の == 比較は信用できません。

ラッパークラス同士の値を比較する場合は、必ず .equals() メソッドを使用すると覚えてください。

あわせて読む

forループや大量データ処理での注意点

パフォーマンスへの影響が最も顕著に現れるのが、ループ処理です。

例えば、1から1,000,000,000までの数値を合計するプログラムを考えてみましょう。

java
// パフォーマンスが悪い例
Long sum = 0L; // Longはラッパークラス
for (long i = 0; i < 1_000_000_000; i++) {
    sum = sum + i;
}
System.out.println(sum);

このコードでは、ループが1回まわるたびに sum = sum + i; の行で以下の処理が発生しています。

  1. ラッパークラスの sum をプリミティブ型の long にアンボクシング
  2. long 型の i と足し算
  3. 計算結果の long 型を、再び Long 型にオートボクシングして sum に代入

つまり、ループの中で10億回のアンボクシングと10億回のオートボクシング、合計20億回の型変換が繰り返されています。

変換が20億回も続くため、処理に大幅な時間がかかります。

この問題は、合計値を格納する変数をプリミティブ型にするだけで解決します。

java
// パフォーマンスが良い例
long sum = 0L; // longはプリミティブ型
for (long i = 0; i < 1_000_000_000; i++) {
    sum = sum + i;
}
System.out.println(sum);

プリミティブ型の long で合計すれば、ループ内でのオブジェクト生成は発生しません。

参考までに、私の環境(Java 21, Apple M2)で10億回ループを実行したところ、Long 版は約6.5秒、long 版は約0.8秒と、約8倍の差がありました。

この差はGCの負荷も含めると実運用ではさらに開く可能性があります。

sum = sum + i; が1回まわるごとに起きること

ループ1回分の処理(1回目)

  1. ① アンボクシングLong の sum を long に
  2. ② 足し算long 同士で sum + i
  3. ③ オートボクシング結果を Long に戻して代入

新しく作られる Long オブジェクト

※ Long も -128〜127 はキャッシュが使われますが、合計値はすぐにこの範囲を超えるため、以降は1回ごとに新しいオブジェクトが作られます。

10億回ループの実行時間(筆者の環境)

Long 版約6.5秒

long 版約0.8秒

Java 21 / Apple M2 で計測。約8倍の差

オートボクシング・アンボクシングのベストプラクティス

オートボクシングは便利ですが、万能ではありません。

読みやすさを取るか、処理速度を取るか。場面ごとに使い分けます。

オートボクシングを避けたいケース

原則として、以下のケースではオートボクシングの利用を避け、プリミティブ型を積極的に使いましょう。

  • for文、while文などのループ処理: 大量のオブジェクト生成を防ぐため。
  • 科学技術計算や大量のデータ処理: 厳密なパフォーマンスが要求される場面。
  • 低レイテンシが求められるシステム: GCによる一時的な停止を避けるため。

迷ったときは、「数値計算がメインの処理ではプリミティブ型を使う」と決めておくと迷いません。

明示的キャストやintValue()の使いどころ

NullPointerException を避けるために、null の可能性があるラッパークラスをプリミティブ型に変換する際は、明示的な処理を記述してください。

if 文で null チェックをしたうえで、.intValue() のようなメソッドを使って値を取り出すのが最も安全な方法です。

java
public int getStockCount(Integer stock) {
    // 在庫数がnull(未設定など)の場合を考慮
    if (stock == null) {
        return 0; // nullの場合は在庫0として扱う
    }
    // nullでないことを確認してから、intValue()で値を取り出す
    return stock.intValue();
}

null チェックを1行足すだけで、実行時の NullPointerException を防げます。

あわせて読む

読みやすさとパフォーマンスのバランスを取るコツ

ただし、プリミティブ型が向かない場面もあります。

たとえばコレクションを扱う場面では、ラッパークラスが必須です。メソッドの戻り値で「値がない」状態を null で表したい場合も、ラッパークラスのほうが設計の自由度が上がります。

大切なのは、パフォーマンスがクリティカルな箇所を見極め、そこを重点的に最適化するという視点です。

私の経験上、チームのコードレビューでオートボクシングが問題になるのは、バッチ処理や集計ロジックなど大量データを扱う箇所がほとんどです。

通常のビジネスロジックでは可読性を優先してオートボクシングを使い、パフォーマンスが計測で問題になった箇所だけプリミティブ型に書き換える、というアプローチが実務では現実的です。

オートボクシング・アンボクシングのよくある質問

Q. オートボクシングとアンボクシングの違いは?

オートボクシングはプリミティブ型(int等)からラッパークラス(Integer等)への自動変換、アンボクシングはその逆でラッパークラスからプリミティブ型への自動変換です。どちらもJavaコンパイラが自動的に行いますが、変換の方向が異なります。

Q. オートボクシングはいつから使える?

Java 5(2004年リリース)以降で利用できます。ジェネリクスと同時に導入され、コレクションにプリミティブ型の値を格納する際の記述が大幅に簡潔になりました。

Q. Integer比較のキャッシュ問題

Javaの Integer クラスは、パフォーマンス最適化のために -128〜127 の値をキャッシュしています。この範囲の値をオートボクシングすると、新しいオブジェクトを作らずキャッシュ済みの同一オブジェクトを返すため、== で比較しても true になります。標準設定では128以上の値はキャッシュされないため、必ず .equals() を使いましょう。

まとめ:正しい使い分けポイント

オートボクシングとアンボクシングは、Java 5以降のコードなら毎日のように裏で動いている機能です。

仕組みを理解してコードの意図を明確に

オートボクシングは、Javaコンパイラが私たちの代わりに型変換を行ってくれる便利な「シンタックスシュガー(糖衣構文)」です。

裏側では Integer.valueOf() や intValue() の呼び出しが自動で挿入されています。この仕組みを知っていれば、パフォーマンスに影響が出る理由も、NullPointerException が発生する理由も説明できます。

トラブルを防ぐためのポイント再確認

オートボクシング・アンボクシングのトラブルを避けるポイントは、次の3つです。

  1. null に注意: null が代入されたラッパークラスをアンボクシングすると NullPointerException が発生します。必ず null チェックを行いましょう。
  2. 比較は .equals() で: ラッパークラスのオブジェクト同士を比較するときは、== ではなく .equals() メソッドを使うのが鉄則です。
  3. ループ内の多用は避ける: パフォーマンスが要求されるループ処理の中では、意図しないオブジェクトが大量に生成される可能性があるため、プリミティブ型の使用を検討しましょう。

この3つのポイントを押さえるだけで、Javaのコードはより安全で、パフォーマンスの高いものになります。

オートボクシングのようなJavaの基礎を体系的に学び直したい方にはこちらがおすすめです。

「なぜ」を丁寧に解説してくれるので、本記事の内容の背景にある仕組みまで理解が深まります。

REVISIONS / 改訂履歴
REV.DATE内容
32026-09-042026年9月時点の情報で全体を見直し
22026-05-032026年5月時点の情報で全体を見直し
12025-10-09初版
  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

-Java入門