JavaのIntegerを==で比較すると値によって結果が変わる理由を解説する記事のアイキャッチ。

Java

JavaのIntegerを==で比較すると結果が変わる理由|equalsとの違い

AIによる要約

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

新人SE
新人SE
Integerの100は==でtrueなのに、1000はfalseになりました。同じ書き方なのに変わるんですか?
ポンコツSE
ポンコツSE
==は参照比較です。小さいIntegerが再利用される仕組みにより、たまたまtrueになる範囲があります。

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版」の表紙
商品画像:Amazon.co.jp
PR Java初学者向け

スッキリわかるJava入門 第5版

中山 清喬 (著), 国本 大悟 (著), 株式会社フレアリンク (監修)

Java文法とオブジェクト指向を、最初から整理する一冊。

豊富な図とサンプルで、変数、条件分岐、クラス、例外処理まで順番に学べます。Javaの最初の教科書を探している人向けです。

  • Javaを基礎から体系的に学びたい
  • Java 25 LTS対応の入門書を選びたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

Integer同士の==は参照を比較します。一定範囲のIntegerがキャッシュされるため小さい値ではtrueになることがありますが、その挙動へ依存してはいけません。

値比較にはequalsまたはObjects.equalsを使い、Integerとintが混ざる場合はアンボクシングとnullを確認します。キャッシュ外の値とnullをテストへ入れてください。

-Java
-, , , ,