Java | 基礎文法:equals の基礎

Java Java
スポンサーリンク

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(内容が同じ)
Java

null セーフな比較

左側が 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")); // どちらも安全
Java

equals の契約(自作クラスでの必須ルール)

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 と同じキーで生成
    }
}
Java
  • getClass() を使うと「サブクラスとは同値にしない」厳密な方針になります。instanceof は「親子関係で同値になり得る」柔軟な方針ですが、対称性が崩れやすいので注意。

よくある落とし穴(重要ポイントの深掘り)

equals と compareTo の不一致

BigDecimalequals がスケールも比べます。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
Java

equals を壊す可変フィールド

集合に入れた後に、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(深い比較)
Java

Arrays.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
Java

a.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 の違いを意識する。可変キーで集合を壊さない——この型が身につくと、コレクション・検索・比較が意図どおりに動きます。

タイトルとURLをコピーしました