equals の全体像
Java の equals は「二つのオブジェクトが“意味として”同じか」を判定するメソッドです。参照が同じか(同一性)は ==、意味が同じか(同値性)は equals で比べます。標準クラス(String、Integer、List、Map など)は「内容」で equals を定義済み。自作クラスで意味の同一性を扱いたいときは equals(と hashCode)を正しくオーバーライドします。
== と equals の違い(ここが最重要)
参照の同一性(==)
== は「同じインスタンスか」を比べます。メモリ上で同じオブジェクトなら true、内容が同じでも別インスタンスなら false です。
String a = new String("OK");
String b = new String("OK");
System.out.println(a == b); // false(別インスタンス)
Java意味の同値性(equals)
equals は「型が意図した意味で同じか」を比べます。String は文字列内容、List は要素列、BigDecimal は数値の「値」を基準にします(ただし BigDecimal の equals はスケールも比較する点に注意)。
String a = "OK";
String b = "OK";
System.out.println(a.equals(b)); // true(内容が同じ)
Javanull セーフな比較
左側が null だと s.equals("OK") は NPE。常に非 null 側から呼ぶか、Objects.equals(a, b) を使います。
String s = null;
System.out.println("OK".equals(s)); // 安全
System.out.println(java.util.Objects.equals(s, "OK")); // どちらも安全
Javaequals の契約(自作クラスでの必須ルール)
5 つの性質
- 反射的(Reflexive): 常に
x.equals(x)は true - 対称的(Symmetric):
x.equals(y)が true ならy.equals(x)も true - 推移的(Transitive):
x.equals(y)とy.equals(z)が true ならx.equals(z)も true - 一貫性(Consistent): 比較対象が変化しない限り、繰り返し判定結果は変わらない
- null との比較は常に false
この契約を破ると、Set/Map/contains などの挙動が壊れます。
equals をオーバーライドするなら hashCode も必ず
「equals が true の二つは、同じ hashCode を返す」ことが必須です。これを守らないと HashSet/HashMap の検索・配置が狂います。
equals の正しいオーバーライド(テンプレと考え方)
型とフィールドの選定
- 比較対象の型が同じかを先に確認(
getClass()かinstanceofを使う方針を決める) - 同値性を表すフィールドだけを比較(ID なのか、全フィールドなのかを設計で決める)
import java.util.Objects;
public final class User {
private final String id;
private final String name;
public User(String id, String name) {
this.id = Objects.requireNonNull(id);
this.name = Objects.requireNonNull(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 参照同一なら true
if (o == null || getClass() != o.getClass()) return false; // 同じ実装だけを同値とする
User other = (User) o;
return Objects.equals(this.id, other.id); // 「意味」を決めたキーだけで比較
}
@Override
public int hashCode() {
return Objects.hash(id); // equals と同じキーで生成
}
}
JavagetClass()を使うと「サブクラスとは同値にしない」厳密な方針になります。instanceofは「親子関係で同値になり得る」柔軟な方針ですが、対称性が崩れやすいので注意。
よくある落とし穴(重要ポイントの深掘り)
equals と compareTo の不一致
BigDecimal は equals がスケールも比べます。new BigDecimal("1.0").equals(new BigDecimal("1")) は false。一方 compareTo は数値として等しいので 0 を返します。ソートや重複除去のルールに合わせて使い分けを設計してください。
var a = new java.math.BigDecimal("1.0");
var b = new java.math.BigDecimal("1");
System.out.println(a.equals(b)); // false(スケール違い)
System.out.println(a.compareTo(b)); // 0(数値は等しい)
Java浮動小数点の equals と NaN
Double.equals は IEEE 754 に従い、NaN は NaN と等しく、+0.0 と -0.0 は異なる扱いになります。「ほぼ等しい」判定は許容誤差(エプシロン)で自作してください。
static boolean almostEquals(double x, double y, double eps) {
return Math.abs(x - y) <= eps;
}
Javaコレクションと equals
List.contains(x)、Set.add(x)、Map.get(key) は equals を使って一致判定します。キーや要素の equals/hashCode が正しくないと、見つからない/重複が消えないなどのバグになります。
var set = new java.util.HashSet<User>();
set.add(new User("A", "Sato"));
System.out.println(set.contains(new User("A", "Sato"))); // equals/hashCodeが合っていれば true
Javaequals を壊す可変フィールド
集合に入れた後に、equals のキーとなるフィールドを変更すると、セット内の位置と検索が壊れます。equals のキーは不変(immutable)にするか、コレクション投入後に変えない設計にします。
標準 API での equals 活用と補助
Objects.equals と deepEquals
Objects.equals(a, b): どちらかが null でも安全に比較Objects.deepEquals(a, b): 配列のネスト構造まで内容比較(Arrays.deepEqualsと同等)
String a = null, b = "OK";
System.out.println(java.util.Objects.equals(a, b)); // false(安全)
Object[] x = { new String[]{"A","B"} };
Object[] y = { new String[]{"A","B"} };
System.out.println(java.util.Objects.deepEquals(x, y)); // true(深い比較)
JavaArrays.equals と Arrays.deepEquals
配列は equals が参照同一性のままなので、内容比較は Arrays.equals/deepEquals を使います。
int[] xs = {1,2,3};
int[] ys = {1,2,3};
System.out.println(java.util.Arrays.equals(xs, ys)); // true
Java文字列のバリエーション
- 大文字小文字を無視するなら
equalsIgnoreCase - 前後空白や正規化も含めて意味を合わせたいなら、先に正規化してから equals
String s = " ok ";
System.out.println("OK".equalsIgnoreCase(s.trim())); // 前処理後に比較
Javaレコード(record)は equals/hashCode を自動生成
Java の record は「成分(フィールド)での同値性」を自動で提供します。ドメインの値オブジェクトに適しています。
record Money(int amount, String currency) {}
System.out.println(new Money(100, "JPY").equals(new Money(100, "JPY"))); // true
Java例題で身につける
例 1: ID 同一なら同値とするエンティティ
import java.util.Objects;
public final class Product {
private final String id;
private final String name;
public Product(String id, String name) {
this.id = Objects.requireNonNull(id);
this.name = Objects.requireNonNull(name);
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Product p)) return false; // Java 16+ パターンマッチ
return Objects.equals(this.id, p.id);
}
@Override public int hashCode() { return Objects.hash(id); }
}
Java「ID が同じなら同値」という設計にすると、Map のキーや Set の重複判定が直感的になります。
例 2: 配列・ネスト構造の内容比較
String[][] a = {{"A","B"},{"C"}};
String[][] b = {{"A","B"},{"C"}};
System.out.println(java.util.Arrays.deepEquals(a, b)); // true
Javaa.equals(b) は参照比較なので false になります。配列は deepEquals を使うのが型。
例 3: ほぼ等しい浮動小数点の比較
double x = 0.3 * 3, y = 0.9;
System.out.println(x == y); // たまに false(丸め誤差)
System.out.println(almostEquals(x, y, 1e-9)); // 許容誤差で比較
Java仕上げのアドバイス(重要部分のまとめ)
equals は「意味の同値」をコードに刻む道具です。== は参照、equals は意味——この違いをまず体に入れる。自作クラスは equals と hashCode をペアで設計し、契約(反射・対称・推移・一貫・null false)を厳守する。配列は Arrays.equals/deepEquals、null セーフは Objects.equals、浮動小数点は許容誤差、BigDecimal は equals と compareTo の違いを意識する。可変キーで集合を壊さない——この型が身につくと、コレクション・検索・比較が意図どおりに動きます。
