4.

Java の比較演算子|== と equals の違い・Integer キャッシュの罠

編集
この記事の要点
  • == は参照の比較。オブジェクトの中身を比べるなら equals()
  • String== で比べると、リテラルなら通るのに実行時に作った文字列では通らない
  • Integer-128〜127 だけキャッシュされるので == の結果がバラつく
  • null 対策には Objects.equals(a, b)どちらが null でも例外にならない
  • 大小の順序を決めるのは compareTo()equals() と一貫させる

比較演算子の一覧

演算子プリミティブ参照型
== / !=値が等しいか同じインスタンスか
< > <= >=大小使えない(コンパイルエラー)
equals()中身が等しいか(実装しだい)
compareTo()負 / 0 / 正 で順序を返す
instanceofその型か

プリミティブは値で比べられる

int a = 10, b = 10;
System.out.println(a == b);      // true
System.out.println(a >= 5);      // true

char c = 'A';
System.out.println(c == 65);     // true   char は数値として比較される

// 浮動小数点は誤差があるので == で比べない
double x = 0.1 + 0.2;
System.out.println(x == 0.3);                    // false
System.out.println(Math.abs(x - 0.3) < 1e-9);    // true

// NaN はどれとも等しくない
double n = 0.0 / 0.0;
System.out.println(n == n);          // false
System.out.println(Double.isNaN(n)); // true

String を == で比べない

String a = "abc";
String b = "abc";
System.out.println(a == b);          // true   同じリテラルは共有される

String c = new String("abc");
System.out.println(a == c);          // false  別のインスタンス
System.out.println(a.equals(c));     // true   中身は同じ

String d = "ab" + "c";               // コンパイル時に "abc" になる
System.out.println(a == d);          // true

String e = "ab";
String f = e + "c";                  // 実行時に作られる
System.out.println(a == f);          // false  ← 開発中は気づけない

Scanner sc = new Scanner(System.in);
String input = sc.nextLine();        // "abc" と入力しても
System.out.println(input == a);      // false

リテラルどうしでは == が通ってしまうため、テストでは動いて本番で動かないという形で表面化します。文字列の比較は必ず equals() にしてください。

// null の可能性がある変数を左に置かない
String input = null;
// input.equals("abc");                    // NullPointerException

"abc".equals(input);                       // false(安全)
Objects.equals(input, "abc");              // false(安全・こちらが推奨)

// 大文字小文字を無視する
"ABC".equalsIgnoreCase("abc");             // true

// 空白を無視して比べる
"  abc ".trim().equals("abc");             // true

ラッパー型の落とし穴

Integer a = 100, b = 100;
System.out.println(a == b);        // true    -128〜127 はキャッシュ済み

Integer c = 200, d = 200;
System.out.println(c == d);        // false   別インスタンス
System.out.println(c.equals(d));   // true

int e = 200;
System.out.println(c == e);        // true    片方が int ならアンボックス化される

Integer f = null;
// System.out.println(f == 200);   // NullPointerException(アンボックス化で落ちる)
System.out.println(Objects.equals(f, 200));   // false(安全)

小さい値では == が通り、大きい値で通らなくなるため、テストデータの値によって挙動が変わります。ラッパー型は常に equals() で比べてください。

equals と hashCode

public class User {
    private final int id;
    private final String name;

    public User(int id, String name) { this.id = id; this.name = name; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User other)) return false;
        return id == other.id && Objects.equals(name, other.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

equals() を書いたら hashCode() も必ず書きます。片方だけだと HashMapHashSet で「入れたのに見つからない」現象が起きます。

Set<User> set = new HashSet<>();
set.add(new User(1, "田中"));
System.out.println(set.contains(new User(1, "田中")));
// hashCode を実装していないと false になる

// record なら equals / hashCode / toString が自動生成される
public record UserRecord(int id, String name) {}
System.out.println(new UserRecord(1, "田中").equals(new UserRecord(1, "田中")));  // true

大小の比較(compareTo)

System.out.println("apple".compareTo("banana"));   // 負(apple のほうが前)
System.out.println("b".compareTo("a"));            // 正
System.out.println("a".compareTo("a"));            // 0

Integer.compare(1, 2);          // -1
Double.compare(1.5, 1.5);       // 0
LocalDate.of(2026, 9, 7).isBefore(LocalDate.now());

// 戻り値の符号だけを見る(1 や -1 とは限らない)
if ("apple".compareTo("banana") < 0) { }
// if (a.compareTo(b) == -1) { }      ← 保証されていない

// 並べ替え
List<User> users = new ArrayList<>();
users.sort(Comparator.comparingInt(User::getId));
users.sort(Comparator.comparing(User::getName).reversed());
users.sort(Comparator.comparingInt(User::getAge)
                     .thenComparing(User::getName));

// null を含む場合
users.sort(Comparator.comparing(User::getName,
        Comparator.nullsLast(Comparator.naturalOrder())));

compareTo() の戻り値は符号だけが意味を持ちます== -1== 1 で判定してはいけません。

null の扱い

String s = null;

System.out.println(s == null);              // true   null 判定は == でよい
// System.out.println(s.equals(null));      // NullPointerException
System.out.println(Objects.equals(s, null)); // true

// null と空文字を区別する
System.out.println(s == null || s.isEmpty());

// 既定値を用意する
String name = Objects.requireNonNullElse(s, "名無し");

// 引数の検証(早い段階で落とす)
public void run(String value) {
    Objects.requireNonNull(value, "value は必須です");
}

詳細は nullもしくは空文字の判定 を参照してください。

よくある間違いのまとめ

書き方問題正しい書き方
str1 == str2参照を比べているObjects.equals(str1, str2)
intObj1 == intObj2128 以上で false になるintObj1.equals(intObj2)
d1 == d2double誤差で一致しないMath.abs(d1 - d2) < 1e-9
value.equals("x")valuenull だと落ちる"x".equals(value)
a.compareTo(b) == -1-1 とは限らないa.compareTo(b) < 0
equals だけ実装Set / Map で見つからないhashCode も実装する
bd1.equals(bd2)BigDecimalスケールが違うと falsebd1.compareTo(bd2) == 0

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 単項演算子
  2. 算術演算子
  3. 代入演算子
  4. 比較演算子
  5. 論理演算子

最近更新/作成されたページ