Javaのラッパークラスとは?intとIntegerの違いと使い分け

最終更新日

Schoo Java入門 中級 第8回 Webアプリケーションにおける状態管理

数値ならintで扱えるのに、なぜList<Integer>や(Integer) session.getAttribute(...)にはIntegerが登場するのでしょうか。Integerは、intの数値をオブジェクトとして扱うためのクラスです。Listなど、オブジェクトを扱う仕組みに数値を渡すときに役立ちます。

ただし、intとIntegerは別の型です。Integerにはnullを代入できますが、nullを計算に使うとエラーになります。この記事では、基本データ型と参照型の違いから振り返り、ラッパークラスが必要な場面と使い分けを説明します。

本記事は、Schoo「Java入門 中級」第8回「Webアプリケーションにおける状態管理」で扱う、基本データ型・ラッパークラスとセッションへの保存を補う記事です。授業を受けていない方にも、数量を扱う具体例で説明します。

まず確認:intは基本データ型、Integerは参照型

同じ3を表示する、2種類の変数

int count = 3;
Integer boxed = 3;
System.out.println(count);
System.out.println(boxed);

どちらも表示結果は3ですが、変数が持つ値の種類は違います。intのcountは数値の3を持ち、Integerのboxedは「数値3を扱うオブジェクトへの参照」を持ちます。参照は、その変数がどのオブジェクトを使うかを示すものです。

intの変数countには数値3が入る。Integerの変数boxedには参照が入り、その矢印の先に数値3を持つIntegerオブジェクトがある。
上段は変数の中に数値、下段は変数からオブジェクトへの矢印です。参照は図の矢印で表しており、変数に「参照」という文字列が入るわけではありません。
型の種類変数が扱うものと例
基本データ型数値や真偽値などの値そのもの。例:int、double、boolean。nullは代入できない。
参照型オブジェクトへの参照、またはどのオブジェクトも指さないnull。例:Integer、String、自分で作ったクラス、配列。

int[]も配列なので参照型です。配列の各要素はintの数値ですが、配列を扱う変数は配列オブジェクトへの参照を持ちます。「数値に関係する型はすべて基本データ型」とは限りません。

図は変数とオブジェクトの関係を簡略化したものです。実際のメモリ番地や、必ずどの領域に配置されるかを表した図ではありません。参照型そのものの考え方は、Javaの参照型とは?変数にオブジェクトそのものではなく場所が入る考え方でも扱っています。

ラッパークラスは、基本データ型をオブジェクトとして扱うためのクラス

Integerのように、基本データ型の値をオブジェクトとして扱うために用意されたクラスをラッパークラスと呼びます。intに対応するものがInteger、booleanに対応するものがBooleanです。

基本データ型対応するラッパークラス
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Integerはjava.langパッケージのクラスなので、利用時にimportを書く必要はありません。また、Integerの方が大きな整数を保存できる、という違いではありません。扱える整数の範囲はintと同じです。intの上位版と考えて、すべて置き換える必要もありません。

使いどころ1:Listに数値をまとめて入れる

注文数量をリストで扱いたい場合、List<int>とは書けません。Listの型引数、つまり< >内には基本データ型を指定できないため、List<Integer>と書きます。

WrapperListExample.java:数量3と5を合計する

import java.util.ArrayList;
import java.util.List;

public class WrapperListExample {
    public static void main(String[] args) {
        List<Integer> quantities = new ArrayList<>();
        quantities.add(3);
        quantities.add(5);

        int total = 0;
        for (Integer quantity : quantities) {
            total += quantity;
        }
        System.out.println(total); // 8
    }
}

このコードの表示結果は8です。add(3)とadd(5)ではintの数値がIntegerへ自動変換され、合計するときはIntegerからintの数値が自動で取り出されます。Listの中ではIntegerとして扱い、合計の計算にはintを使うという役割分担です。

左側のListはリストの共通の型、new ArrayList<>()は今回使う具体的なリストです。ここでは既存のリストの書き方として扱い、数値を要素にする場合の<Integer>に注目してください。

自動変換:ボクシングとアンボクシング

メソッド内:intとIntegerを行き来する

int count = 3;
Integer boxed = count;  // intからIntegerへ
int restored = boxed;  // Integerからintへ
System.out.println(restored); // 3
  • intなどの基本データ型から対応するラッパークラスへ変換することをボクシングと呼びます。この代入のように自動で行われるものがオートボクシングです。
  • Integerなどのラッパークラスから、対応する基本データ型の値を取り出すことをアンボクシングと呼びます。
intの3はボクシングによってIntegerとして扱えるようになる。Integerから数値3を取り出すのがアンボクシング。nullには取り出せる数値がないため、intへの代入時にNullPointerExceptionとなる。
コードの右辺を、左辺で必要な型として扱うために変換します。図の下段は失敗例です。nullと0は区別します。

自動変換で何をしているかは、次のようにメソッドを明示すると読み取りやすくなります。

同じ変換をvalueOfとintValueで明示する

int count = 3;
Integer boxed = Integer.valueOf(count);
int restored = boxed.intValue();
System.out.println(restored); // 3

valueOf(count)で数値を表すIntegerを取得し、intValue()でintの数値を取り出しています。元のcountという変数の型がIntegerへ変わるわけではありません。countは引き続きintです。

Integerでは、既存のオブジェクトが再利用される場合があります。そのため、ボクシングのたびに必ず新しいオブジェクトが作られる、とは考えません。また、Integer.parseInt("3")は文字列を数値として読み取る処理であり、ここで扱うボクシングとは別です。

使いどころ2:セッションへ数値を保存する

セッションのsetAttributeは、名前とObject型の値を受け取ります。intはオブジェクトではありませんが、次のコードはコンパイルできます。3がIntegerへ自動変換されて渡されるためです。

Servlet内:取得済みのsessionへ数量を保存する

session.setAttribute("quantity", 3);

ここでは、有効なHttpSessionをsessionという変数に取得済みとします。取り出す側も同じセッションを使います。以下のSystem.out.printlnは動作の説明用で、ブラウザではなくサーバー側の出力です。

Servlet内:保存した数量で、単価500円の合計を計算する

Integer quantity = (Integer) session.getAttribute("quantity");
if (quantity == null) {
    System.out.println("数量が保存されていません");
} else {
    int total = quantity * 500;
    System.out.println(total);
}

数量3を保存した後なら1500と出力されます。getAttributeの戻り値の型はObjectなので、保存した型であるIntegerにキャストして受け取ります。その後のquantity * 500で、Integerからintへのアンボクシングが行われます。

キャストとアンボクシングは別です。(Integer)は、受け取った参照をIntegerとして扱う指定です。計算のためにintの数値を取り出すのがアンボクシングです。

quantityという属性がなければgetAttributeはnullを返します。この例では、計算する前にnullを確認し、「数量が保存されていません」と出力します。セッションそのものの有無は、このコードに入る前に確認する必要があります。

保存したものが文字列の"3"だった場合、(Integer)と書いても数値には変換できず、ClassCastExceptionになります。保存した型と取り出す型を合わせることが必要です。

nullは「0」ではなく、オブジェクトがない状態

値数量としての意味を決める例
Integer quantity = 0;数値の0がある。数量0として計算できる。
Integer quantity = null;数値を持つオブジェクトがない。アプリの設計によって、未入力や未保存などとして扱う。
int quantity = null;intにはnullを代入できないため、コンパイルエラー。

Integerを使うと「値がない状態」と「0という値がある状態」を分けられます。ただし、nullが未入力なのか未保存なのかは、アプリ側で意味を決める必要があります。次は、nullをintへ代入してしまう失敗例です。

失敗例:nullからはintの数値を取り出せない

Integer missing = null;
int value = missing; // ここでNullPointerException

このコードはコンパイルできますが、2行目の実行時にNullPointerExceptionになります。自動変換でも、nullから数値を取り出すことはできません。missing + 1やmissing > 0のような計算・比較でも同じ注意が必要です。

メソッド内:値がない場合と、計算できる場合を分ける

Integer quantity = null;
if (quantity == null) {
    System.out.println("数量が未入力です");
} else {
    int total = quantity * 500;
    System.out.println(total);
}

この例では「数量が未入力です」と出力され、計算は行いません。nullを無条件で0に置き換えると、入力漏れを見逃すことがあります。値がないときの処理を決めてから、計算に使います。

Integer同士の数値を比べるとき、==に頼らない

メソッド内:数値の一致と、参照の一致を区別する

Integer left = 1000;
Integer right = 1000;

System.out.println(left.equals(right)); // true:数値が等しい
System.out.println(left == right); // 数値ではなく参照を比較する

1つ目の表示はtrueです。Integerのequalsは、相手もIntegerで同じ整数値かを調べます。一方、両辺がIntegerの==は、同じオブジェクトを指しているかを調べます。数値の一致を調べる目的とは違います。

2つ目の表示結果は、オブジェクトが再利用されるかに影響されます。上の値でfalseになる環境でも、別の値ではtrueになることがあります。「試したら==で比較できた」と判断せず、Integer同士の数値比較にはequalsを使います。

メソッド内:左側がnullかもしれない場合

Integer left = null;
Integer right = 1000;
boolean sameNumber = left != null && left.equals(right);
System.out.println(sameNumber); // false

このコードは「左側に数値があり、右側と同じIntegerの値か」を確認する例で、結果はfalseです。&&の左側がfalseなら右側を実行しないので、nullに対してequalsを呼びません。両方がnullなら数値が等しいとする、という判定ではありません。

なお、片方がintのleft == 1000では、Integer側がアンボクシングされて数値比較になります。この場合もleftがnullなら例外になるので、==の左右の型まで見ることが大切です。

参照型でも、Integerの中の数値を書き換えるわけではない

メソッド内:aを4にしても、bは3のまま

Integer a = 3;
Integer b = a;
a = 4;

System.out.println(a); // 4
System.out.println(b); // 3

最初はaとbが同じIntegerオブジェクトを参照しています。その後のa = 4;では、aに数値4を表すIntegerへの参照を代入します。bの参照はそのままなので、出力はaが4、bが3です。

Integerは、作成後にオブジェクト内部の数値を変更できないクラスです。この性質を「不変」といいます。参照型だからといって、どのクラスでも内部の値を自由に書き換えられるわけではありません。

intとIntegerは、必要な扱い方に合わせて選ぶ

やりたいこと選び方
値が必ずあり、件数や合計を計算する通常はint。オブジェクトとして扱う必要がなければ、Integerへ置き換えなくてよい。
Listに整数を入れるList<Integer>を使う。List<int>とは書けない。
セッションなど、Objectを受け取る場所へ数値を渡すIntegerとして扱う。intで渡すと自動でボクシングされる。
値がない状態と0を区別するIntegerとnullで表す方法がある。計算の前にnullの場合の処理を決める。

基本データ型と参照型の違いが分かると、「なぜIntegerなのか」「なぜnullで失敗するのか」がつながります。ラッパークラスはintの代わりにいつでも使うものではなく、数値をオブジェクトとして扱いたい場面をつなぐためのものです。

確認問題

  1. int、Integer、String、int[]を基本データ型と参照型に分けてください。また、intとIntegerの変数がそれぞれ何を持つか説明してください。
  2. 数量をまとめるためにList<int> quantitiesと書いたところ、コンパイルエラーになりました。どの型に直しますか。また、quantities.add(3)ではどのような変換が行われますか。
  3. session.setAttribute("quantity", 3)で保存した値を取り出します。対応するラッパークラスは何ですか。また、その属性が未保存なのに、取得した値をそのままintへ変換するとどうなりますか。
  4. Integer a = 3; Integer b = a; a = 4; の順に実行した後、aとbを表示するとそれぞれ何になりますか。参照型なのにbが変わらない理由も答えてください。
  5. Integer同士の数量を==で比較する方法が不適切なのはなぜですか。左側に数値があるときだけ値の一致を確認したい場合、nullを考慮した判定をどう書きますか。

演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。

まとめ

  • intは基本データ型、Integerは参照型。変数が持つものが違う。
  • ラッパークラスを使うと、基本データ型の値をオブジェクトとして扱える。
  • intからIntegerへの変換がボクシング、Integerからintの値を取り出すのがアンボクシング。
  • nullには取り出せる数値がない。0とは区別し、計算の前に確認する。
  • Integer同士の数値比較はequalsを使い、nullに対する呼び出しを避ける。

関連記事

シェアする