Contents
1. 基本は「参照か値か」だけ
| 演算子 / メソッド | 比較対象 | 何が評価されるか |
|---|---|---|
== |
任意の型(プリミティブ・オブジェクト) | 参照そのもの(同一メモリ位置) ※プリミティブの場合は値そのまま |
equals() |
オブジェクト | クラスが実装した 内容(論理的な等価性) Object.equals は参照比較しか行わないが、ほとんどのクラスでオーバーライドされている |
- 同一オブジェクトか知りたい →
== - 「見た目」や「意味的に等しいか」判定したい →
equals()
例)文字列リテラルはプールに格納されるため
s1 == s2が true になることがあるが、実装上は常にequals()を使うべきです。
2. String の比較で注意すべき落とし穴
2‑1. リテラル vs new String
|
1 2 3 4 5 6 7 8 |
String a = "hello"; // プールに登録 String b = "hello"; String c = new String("hello"); // 明示的に新規オブジェクト System.out.println(a == b); // true(同一インスタンス) System.out.println(a == c); // false(別インスタンス) System.out.println(a.equals(c)); // true |
- リテラル同士は
==が true になることがあるが、外部入力やnew String()で生成した文字列は必ず別オブジェクトです。 - 実務では常に
equals()(またはObjects.equals)を使うのが安全です。
2‑2. ユーザー入力・外部データ
|
1 2 3 4 5 6 |
String input = scanner.nextLine(); // ユーザーから取得 if (input.equals("yes")) { // 推奨 … } |
input == "yes" はほぼ常に false になるため、文字列比較では == を使用しないでください。
3. ラッパークラス(オートボクシング)とキャッシュ
3‑1. キャッシュがある範囲
| クラス | JDK がキャッシュする範囲(valueOf / オートボクシング時) |
|---|---|
Integer |
-128 ~ 127 |
Long |
-128 ~ 127 |
Short |
-128 ~ 127(※全てがキャッシュされるわけではなく、この範囲は保証) |
Byte |
全ての値(‑128 ~ 127)は必ずキャッシュ |
Character |
\u0000 ~ \u007F(0〜127) |
ポイント:
Shortが「全体を」キャッシュするという情報は誤りです。JDK の仕様上、-128〜127 のみが保証されます。
3‑2. キャッシュに依存しない比較
|
1 2 3 4 5 6 7 8 9 10 11 |
Integer i1 = 127; // キャッシュ対象 Integer i2 = 127; System.out.println(i1 == i2); // true(キャッシュ) Integer i3 = 128; // キャッシュ外 Integer i4 = 128; System.out.println(i3 == i4); // false // 安全な比較は常に equals() System.out.println(i3.equals(i4)); // true |
==に頼るとキャッシュの有無で結果が変わり、バグの温床になる- プリミティブへアンボクシングしてから
==を使うか、equals()を使用してください。
4. Null 安全な比較
4‑1. NPE を防ぐ書き方
|
1 2 3 4 |
if (obj != null && obj.equals(target)) { … } // 従来の方法 // 推奨 if (Objects.equals(obj, target)) { … } // 両側が null でも安全 |
Objects.equals(a,b) の内部実装は
|
1 2 |
return a == b || (a != null && a.equals(b)); |
なので、null チェックを毎回書く必要がなくなります。
4‑2. 複数フィールドの比較例
|
1 2 3 4 5 6 7 8 9 |
public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person p = (Person) o; return age == p.age && Objects.equals(name, p.name) && // null 安全 Objects.equals(email, p.email); } |
Objects.equals を使うだけでコードがシンプルになり、レビュー時の見落としが減ります。
5. カスタムクラスにおけるベストプラクティス
| 項目 | 推奨実装 |
|---|---|
| equals の基本形 | if (this == o) return true; if (!(o instanceof MyClass)) return false; |
| hashCode と整合性 | Objects.hash(field1, field2 …) を使用 |
| null 安全 | Objects.equals で比較 |
同一性チェックに == を使うケース |
enum、シングルトン、Class<?> オブジェクトなど「インスタンスが唯一」な型 |
例:完全実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public final class Person { private final String name; private final int age; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person p = (Person) o; return age == p.age && Objects.equals(name, p.name); } @Override public int hashCode() { return Objects.hash(name, age); } } |
hashCodeを忘れるとHashMap/HashSetで不整合が発生します。equalsとhashCodeは必ず対に実装しましょう。
6. コードレビュー時のチェックリスト
|
1 2 3 4 5 6 |
[ ] String / Character 系の比較は equals()/Objects.equals を使用しているか [ ] ラッパークラス (Integer, Long, Short, Byte) の比較で == が残っていないか [ ] null 安全な比較に Objects.equals が使われているか [ ] カスタムクラスの equals と hashCode が正しくオーバーライドされているか [ ] enum や Class オブジェクト以外で == を使用していないか |
このリストをレビュー時に必ず確認すれば、比較バグの多くは未然に防げます。
7. まとめ:安全な比較のための3つのルール
- 内容が重要なら
equals()(またはObjects.equals)を使う -
String、ラッパークラス、カスタムオブジェクトすべてに当てはまります。 -
==は「同一インスタンスか」だけを判定したいときに限定する -
enum、シングルトン、Class<?>など唯一性が保証された型でのみ使用。 -
キャッシュやオートボクシングに依存しないコードを書く
- ラッパークラスは常に
equals()、またはプリミティブへアンボクシングしてから==を使う。
これらの指針を守れば、Java の比較に起因するバグは格段に減少し、保守性と可読性が向上します。ぜひ日常のコーディング・レビューで活用してください。