AIによる要約
Integer同士の==は数値ではなく参照を比較します。ただしInteger.valueOfが一定範囲のインスタンスを再利用するため、小さい値ではtrue、大きい値ではfalseとなることがあり、さらに混乱を招きます。数値として同じか確認するならequals、大小比較ならcompareやintValueを使います。Integerがnullの可能性を持つ場合はObjects.equalsや明示的なnull判定を選びます。


Stringの==が参照比較だと知っていても、Integerは数値だから==でよいと思いやすいところです。プリミティブ型のintなら==は値を比較しますが、ラッパークラスのInteger同士では参照を比較します。
さらにオートボクシングとIntegerキャッシュがあるため、テスト値100では動き、本番のID1000では失敗するという厄介な挙動になります。比較対象の型を確認し、値比較と参照比較を使い分けます。
この記事のポイント
- Integer同士の==は参照を比較する
- 一定範囲のIntegerはキャッシュされる
- 値の一致はequalsまたはObjects.equalsで確認する
- Integerとintの比較ではアンボクシングが起きる
- nullのIntegerをアンボクシングするとNullPointerExceptionになる
intの==とIntegerの==は意味が違う
プリミティブ型のintにはオブジェクト参照がないため、==は数値を比較します。Integerはオブジェクトなので、Integer同士の==は同じインスタンスかを比較します。
int primitiveA = 1000;
int primitiveB = 1000;
System.out.println(primitiveA == primitiveB); // true
Integer objectA = 1000;
Integer objectB = 1000;
System.out.println(objectA == objectB); // falseになることがあるソースコード上では同じ数字に見えても、変数型が違えば演算の意味が変わります。IDEの型表示やメソッド定義を確認し、見た目だけで判断しません。
小さい値だけtrueになるIntegerキャッシュ
オートボクシングでは、Integer.valueOf相当の処理でintからIntegerを作ります。Javaは少なくとも-128から127のIntegerを再利用するため、この範囲では同じ参照になりやすく、==がtrueになります。
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true
Integer x = 1000;
Integer y = 1000;
System.out.println(x == y); // falseこの範囲を暗記して==を使い分けるのは誤りです。JVM設定や生成方法へ依存せず、値比較なら常に値比較用の書き方を選びます。
値を比較するならequalsを使う
Integerのequalsは、相手もIntegerで数値が同じ場合にtrueを返します。キャッシュ範囲やインスタンス生成方法には依存しません。
Integer expected = 1000;
Integer actual = Integer.valueOf("1000");
System.out.println(expected.equals(actual)); // trueただしexpectedがnullならメソッド呼び出しでNullPointerExceptionです。定数側が確実にnullでないなら定数側から呼ぶか、両方nullの可能性を含むならObjects.equalsを使います。
nullを含むならObjects.equals
DBのnullable列、画面の任意入力、Map#getの戻り値など、Integerがnullになる経路はあります。Objects.equalsは両方nullならtrue、片方だけnullならfalseを返します。
Integer expected = null;
Integer actual = null;
System.out.println(Objects.equals(expected, actual)); // true業務上、両方nullを同じとみなしてよいかは確認が必要です。未入力同士を一致とする検索条件と、必須IDが両方nullなのを正常とする判断は同じではありません。
Integerとintの比較ではアンボクシングされる
片方がintの場合、Integerはintへ自動変換され、数値比較になります。そのため次のコードは1000でもtrueです。
Integer objectValue = 1000;
int primitiveValue = 1000;
System.out.println(objectValue == primitiveValue); // trueしかしobjectValueがnullなら、intへ変換する時点でNullPointerExceptionになります。型が違う比較がたまたま動いている場合は、nullの契約と型を統一できないか確認します。
IDやステータス比較で起きる現場バグ
DBから取得したIntegerのID、画面Formの区分値、外部APIのコードを==で比べると、値の範囲によって分岐が変わります。小さいテストデータだけを使う単体テストでは見逃します。
// NG: 参照比較
if (order.getStatusCode() == expectedStatusCode) {
approve(order);
}
// 値比較
if (Objects.equals(order.getStatusCode(), expectedStatusCode)) {
approve(order);
}区分値が有限ならIntegerではなくenumを使うと、値の範囲と名前を型で表せます。数値コードをどの層でenumへ変換するか、既存設計に合わせて検討します。
JUnitではキャッシュ外とnullを入れる
比較ロジックのテストでは、100だけでなく1000、別インスタンス、nullを含めます。==を誤用した実装ならキャッシュ外の値で失敗します。
@ParameterizedTest
@CsvSource({"100", "1000", "2147483647"})
void 同じ数値のIntegerを一致とみなす(int source) {
Integer a = Integer.valueOf(source);
Integer b = Integer.valueOf(String.valueOf(source));
assertTrue(Objects.equals(a, b));
}new Integerは非推奨なので、別インスタンスを作るためだけに本番コードへ持ち込まないでください。テスト目的もvalueOfと文字列変換で十分表せます。
現場レビューでよくある指摘
Integer比較のレビューでは、演算子だけでなく左右の型、nullの可能性、値の範囲、業務上の一致条件を確認します。
// レビューコメント例
Integer同士の==は参照比較です。小さい値では偶然trueになるため、
値比較ならObjects.equalsへ変更してください。
// レビューコメント例
右辺がintなので自動アンボクシングされます。
左辺がnullの場合にNullPointerExceptionになる契約を確認してください。
// レビューコメント例
この数値はステータス区分なので、Integerのまま分岐を増やすより
enumへ変換できないか検討してください。『今のデータでは動く』ではなく、キャッシュ外の値とnullで同じ意味になるか説明します。比較対象がIDなのか区分値なのかによって、equalsだけでなく型の見直しも行います。
提出前のセルフチェック
レビュー前に確認すること
- 比較対象がintかIntegerか確認したか
- Integer同士へ==を使っていないか
- キャッシュ外の値をテストしたか
- nullの可能性を確認したか
- アンボクシングでNPEにならないか
- Objects.equalsで両方nullを同一視してよいか
- 区分値をenumにできないか
- 比較の意図が変数名から分かるか
Javaの型変換と比較を整理する参考書
オートボクシング、ラッパークラス、equalsはJavaの基本文法とオブジェクト指向の境目です。入門書でまとまって確認すると、値によって結果が変わる誤実装を避けられます。
スッキリわかるJava入門 第5版
Java文法とオブジェクト指向を、最初から整理する一冊。
豊富な図とサンプルで、変数、条件分岐、クラス、例外処理まで順番に学べます。Javaの最初の教科書を探している人向けです。
- Javaを基礎から体系的に学びたい
- Java 25 LTS対応の入門書を選びたい
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
まとめ
Integer同士の==は参照を比較します。一定範囲のIntegerがキャッシュされるため小さい値ではtrueになることがありますが、その挙動へ依存してはいけません。
値比較にはequalsまたはObjects.equalsを使い、Integerとintが混ざる場合はアンボクシングとnullを確認します。キャッシュ外の値とnullをテストへ入れてください。
