Javaでマスターするequalsメソッドの13のポイント – Japanシーモア

Javaでマスターするequalsメソッドの13のポイント

Javaのequalsメソッドを使いこなすための詳細ガイドJava
この記事は約25分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

このサービスは複数のSSPによる協力の下、運営されています。

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めばJavaのequalsメソッドを完全に理解できるようになります。

Javaプログラミングにおいて、オブジェクト同士をどう比較するかは非常に重要な問題です。

特に初心者の方は、==演算子を使ってしまいがちですが、それでは期待した結果を得られない場合が多いです。

equalsメソッドの正しい使い方をマスターすることで、より効率的なプログラムを作成する道が開けます。

●equalsメソッドとは

○基本的な定義と使い方

Javaでオブジェクトの比較を行う際に使われるメソッドが、equalsメソッドです。

このメソッドは、JavaのObjectクラスに定義されているため、Javaで作成されるすべてのクラスには、このequalsメソッドが存在します。

基本的な使い方は非常にシンプルで、次のようになります。

オブジェクト1.equals(オブジェクト2);

このメソッドがtrueを返すとき、オブジェクト1オブジェクト2は等しいとみなされます。

逆に、falseが返されるときは、等しくないと判断されます。

○なぜequalsメソッドが必要なのか

一見シンプルに見えるこのequalsメソッドですが、なぜこれが必要なのでしょうか。

それは、==演算子が行うのは「参照比較」であり、内容が同じでも異なるインスタンスであればfalseと評価されてしまうからです。

例えば、次のようにStringクラスで考えてみましょう。

String str1 = new String("Java");
String str2 = new String("Java");

boolean result = str1 == str2;  // これはfalseになる

str1str2は内容は同じですが、異なるインスタンスです。

そのため、==で比較するとfalseになります。

しかし、equalsメソッドを使うと、次のように内容が同じであるかどうかで比較されます。

boolean result = str1.equals(str2);  // これはtrueになる

このように、equalsメソッドは「内容の比較」を行います。

この違いを理解することで、より正確なオブジェクト比較が行えます。

●equalsメソッドの基本的な使い方

基本的な使い方を理解するために、いくつかの具体的な例を見ていきましょう。

ここでは、独自のクラスにequalsメソッドをオーバーライドする際のポイントについても解説します。

○サンプルコード1:基本的なString比較

まずは、最もよく使われるStringクラスでのequalsメソッドの使用例から見ていきましょう。

// JavaでのString比較のサンプルコード
public class Main {
    public static void main(String[] args) {
        String str1 = "Java";
        String str2 = new String("Java");

        boolean isEqual = str1.equals(str2); // trueが出力される
        System.out.println("文字列が等しいかどうか:" + isEqual);
    }
}

このコードは、str1str2という二つのStringオブジェクトが等しいかどうかをequalsメソッドで比較しています。

この例では、str1.equals(str2)trueを返すため、結果として「文字列が等しいかどうか:true」と表示されます。

○サンプルコード2:独自クラスでのequalsメソッドのオーバーライド

次に、独自のクラスでequalsメソッドをオーバーライドする方法について見ていきます。

これはJavaプログラミングで非常に頻繁に行われる操作です。

// 独自クラスでのequalsメソッドのオーバーライド例
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true; // 参照が同一ならtrue
        if (obj == null || getClass() != obj.getClass()) return false; // nullまたは型が異なる場合はfalse

        Person person = (Person) obj; // 型キャスト
        return age == person.age && name.equals(person.name); // 年齢と名前が同じならtrue
    }
}

この独自クラスPersonでは、nameageの2つのフィールドを持っています。このクラスにequalsメソッドをオーバーライドしています。

このコードでは、まず引数として渡されたオブジェクトがnullでなく、型がPersonであることを確認しています。

その後、年齢と名前が一致する場合にtrueを返しています。

この独自クラスを用いた比較を行う例は次の通りです。

// 独自クラスのequalsメソッドを用いた比較
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);

        boolean isEqual = person1.equals(person2); // trueが出力される
        System.out.println("Personオブジェクトが等しいかどうか:" + isEqual);
    }
}

この例では、person1.equals(person2)trueを返すので、「Personオブジェクトが等しいかどうか:true」と表示されます。

○サンプルコード3:配列でのequalsメソッドの使い方

配列の場合、特にプリミティブ型の配列ではequalsメソッドが期待する動作をしません。

これは、配列がオブジェクトであるため、Objectクラスのequalsメソッドが適用され、それは参照比較(==)を行います。

配列の要素自体が等しいかどうかを比較するためには、Arrays.equalsメソッドを使用するのが一般的です。

整数型の配列を比較するJavaのサンプルコードを紹介します。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] array1 = {1, 2, 3};
        int[] array2 = {1, 2, 3};
        int[] array3 = {1, 2, 4};

        // 配列の内容が等しいかどうかを確認
        boolean isEqual1 = Arrays.equals(array1, array2); // true
        boolean isEqual2 = Arrays.equals(array1, array3); // false

        System.out.println("配列1と配列2が等しいか:" + isEqual1);
        System.out.println("配列1と配列3が等しいか:" + isEqual2);
    }
}

このサンプルコードではjava.util.Arraysクラスのequalsメソッドを使用して、二つの整数型の配列array1array2が等しいかどうかを比較しています。

具体的には、Arrays.equals(array1, array2)trueを返す一方、Arrays.equals(array1, array3)falseを返します。

これは、array1array2の全ての要素が一致しているためです。

実行結果として、次のような出力が得られます。

配列1と配列2が等しいか:true
配列1と配列3が等しいか:false

○サンプルコード4:nullとの比較

null値との比較も多くのJavaプログラマーが陥る問題です。

equalsメソッドは通常、nullが引数である場合にはfalseを返すように設計されています。

そのため、nullチェックをしてからequalsメソッドを呼び出す必要があります。

nullとの比較をするJavaのサンプルコードを紹介します。

public class Main {
    public static void main(String[] args) {
        String str1 = "Java";
        String str2 = null;

        boolean isEqual = str1.equals(str2);  // falseが出力される
        // nullチェックをしてからequalsメソッドを使用
        boolean isEqualWithNullCheck = (str2 != null) && str1.equals(str2);

        System.out.println("nullとの比較:" + isEqual);
        System.out.println("nullチェックを含む比較:" + isEqualWithNullCheck);
    }
}

このサンプルコードでは、str1という文字列とnull値を比較しています。

この状態でstr1.equals(str2)を実行すると、falseが返されます。さらに、nullチェックを追加した場合の比較も行っています。

それはboolean isEqualWithNullCheck = (str2 != null) && str1.equals(str2);であり、この式もfalseを返します。

実行結果としては、次のような出力がされます。

nullとの比較:false
nullチェックを含む比較:false

●equalsメソッドの詳細な対処法

equalsメソッドが一見単純に見えても、実際には多くの状況で使い方を工夫する必要があります。

特に型が違うオブジェクトとの比較や継承関係にあるクラスでの比較、複数フィールドを持つオブジェクトの比較などは注意が必要です。

○サンプルコード5:型が違うオブジェクトとの比較

Javaでは、型が違うオブジェクト同士をequalsで比較すると、通常はfalseが返ります。

しかし、どのようにfalseが返されるのかを確認するためのサンプルコードを見てみましょう。

public class Main {
    public static void main(String[] args) {
        String str = "123";
        Integer num = 123;

        // StringとIntegerの比較
        boolean isEqual = str.equals(num);
        System.out.println("型が違うオブジェクト同士の比較:" + isEqual);
    }
}
// 出力結果
// 型が違うオブジェクト同士の比較:false

このコードでは、String型のstrInteger型のnumという、型が異なる二つのオブジェクトをequalsメソッドで比較しています。

当然ながら、falseが返されます。

○サンプルコード6:継承関係にあるクラスでの比較

継承関係にあるクラス間でequalsメソッドを適切にオーバーライドすることは、非常に重要です。

下記のサンプルコードは、親クラスと子クラスでequalsメソッドをどのように扱うかを表しています。

class Animal {
    String name;
    public Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    String breed;
    public Dog(String name, String breed) {
        super(name);
        this.breed = breed;
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Animal");
        Dog dog = new Dog("Animal", "Labrador");

        // 親クラスと子クラスの比較
        boolean isEqual = animal.equals(dog);
        System.out.println("親クラスと子クラスの比較:" + isEqual);
    }
}
// 出力結果
// 親クラスと子クラスの比較:false

このコードではAnimalクラスとその子クラスであるDogクラスを定義しています。

equalsメソッドをオーバーライドしていないので、Objectクラスのequalsメソッドが使用され、falseが返されます。

○サンプルコード7:複数フィールドの比較

単一のフィールドだけでなく、複数のフィールドを持つオブジェクトを比較する場合はどうでしょうか。

下記のサンプルコードは、Personクラスにnameageという二つのフィールドがあり、それらを考慮に入れたequalsメソッドのオーバーライドを表しています。

public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        Person person = (Person) obj;

        return age == person.age && name.equals(person.name);
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("John", 25);
        Person person2 = new Person("John", 25);
        Person person3 = new Person("Doe", 30);

        // 複数フィールドを持つオブジェクトの比較
        boolean isEqual1 = person1.equals(person2); // true
        boolean isEqual2 = person1.equals(person3); // false

        System.out.println("複数フィールドを持つオブジェクト同士の比較1:" + isEqual1);
        System.out.println("複数フィールドを持つオブジェクト同士の比較2:" + isEqual2);
    }
}
// 出力結果
// 複数フィールドを持つオブジェクト同士の比較1:true
// 複数フィールドを持つオブジェクト同士の比較2:false

このサンプルコードでは、Personクラスにnameageの二つのフィールドを持ち、これらを全て考慮に入れてequalsメソッドをオーバーライドしています。

その結果、person1person2は等しく、person1person3は等しくないと評価されます。

●equalsメソッドの詳細な注意点

equalsメソッドを効果的に使用するためには、いくつかの注意点があります。

特に、hashCodeメソッドとの関連性、不変オブジェクトでの使用、そしてパフォーマンスへの影響について、具体的なサンプルコードとともに解説します。

○hashCodeメソッドとの関連

equalsメソッドをオーバーライドする場合、hashCodeメソッドも一緒にオーバーライドする必要があります。

これはJavaの標準ライブラリであるjava.utilパッケージ内のコレクションクラス(HashSet、HashMapなど)で重要な役割を果たします。

public class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        Student student = (Student) obj;

        return age == student.age && name.equals(student.name);
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }
}

// メインクラス
public class Main {
    public static void main(String[] args) {
        Student s1 = new Student("John", 25);
        Student s2 = new Student("John", 25);

        HashSet<Student> set = new HashSet<>();
        set.add(s1);
        set.add(s2);

        System.out.println("HashSetの要素数: " + set.size());
    }
}

// 出力結果
// HashSetの要素数: 1

このコード例では、Studentクラスがnameとageをフィールドとして持っており、equalsメソッドとhashCodeメソッドをオーバーライドしています。

その結果、HashSetでs1とs2が同じであると判断され、要素数は1となります。

○不変オブジェクトでの使用

不変オブジェクト(変更不可能なオブジェクト)では、一度インスタンスが生成された後はその状態が変更されないため、equalsメソッドの挙動も変わらない点が利点です。

public final class ImmutablePerson {
    private final String name;
    private final int age;

    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// メインクラス
public class Main {
    public static void main(String[] args) {
        ImmutablePerson p1 = new ImmutablePerson("John", 25);
        ImmutablePerson p2 = new ImmutablePerson("John", 25);

        System.out.println("不変オブジェクトの比較: " + p1.equals(p2));
    }
}

// 出力結果
// 不変オブジェクトの比較: true

このコード例で生成されたImmutablePersonオブジェクトは、不変であるため安全にコレクション等で使用することができます。

○パフォーマンスへの影響

equalsメソッドの実装によっては、特に大きなデータ構造を扱う場合にパフォーマンスに影響を与える可能性があります。

例えば、リストや配列の全要素をループで走査するような実装は避けるべきです。

// パフォーマンスに影響を与える可能性のあるコード(非推奨)
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;

    MyClass myClass = (MyClass) obj;

    for (int i = 0; i < largeArray.length; i++) {
        if (largeArray[i] != myClass.largeArray[i]) return false;
    }
    return true;
}

このようなコードは、largeArrayのサイズが大きい場合に非効率であり、パフォーマンスに影響を与える可能性が高いです。

●equalsメソッドの詳細なカスタマイズ

Javaにおいてequalsメソッドの使用は頻繁であり、そのカスタマイズも多くの場面で求められます。

特定のライブラリを利用する方法や、独自の比較ロジックを追加する方法など、いくつかの手法が存在します。

○サンプルコード8:Apache Commons Langを使った実装

Apache Commons Langライブラリは、Javaの標準ライブラリでは提供されていない便利なメソッドを多数含んでいます。

このライブラリを使えば、equalsメソッドのオーバーライドを簡単に行うことができます。

import org.apache.commons.lang3.builder.EqualsBuilder;

public class Employee {
    String name;
    int age;

    // コンストラクタ、ゲッター、セッターは省略

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Employee) {
            Employee other = (Employee) obj;
            return new EqualsBuilder().append(name, other.name).append(age, other.age).isEquals();
        }
        return false;
    }
}

このコード例では、Apache Commons LangライブラリのEqualsBuilderクラスを用いて、nameageフィールドでの比較を行っています。

これにより、コードが短く、読みやすくなります。

○サンプルコード9:Lombokを使ったシンプルな実装

LombokはJavaの冗長なコードを短縮するためのライブラリです。

特に、@EqualsAndHashCodeアノテーションを使うと、equalsメソッドとhashCodeメソッドの実装を自動で行ってくれます。

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Product {
    String name;
    int price;

    // コンストラクタ、ゲッター、セッターは省略
}

このコードにより、Productクラスにnamepriceフィールドがあり、Lombokが自動で適切なequalsメソッドとhashCodeメソッドを生成してくれます。

○サンプルコード10:カスタム比較ロジックの追加

場合によっては、独自の比較ロジックをequalsメソッドに組み込む必要があります。

public class Score {
    int math;
    int english;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Score)) return false;

        Score other = (Score) obj;

        // 総合得点が同じならば、同じとみなすカスタムロジック
        return (math + english) == (other.math + other.english);
    }
}

このコードでは、Scoreクラスの独自の比較ロジックとして、mathenglishの総合得点が同じであれば同一のオブジェクトとみなすようにしています。

●equalsメソッドの応用例

equalsメソッドは単純なオブジェクト比較以上に多くの場面で活躍します。

ここでは、特によく使用される応用例に焦点を当て、どのようにequalsメソッドを効果的に活用できるかを具体的に解説します。

○サンプルコード11:リスト内でのオブジェクトの探索

Javaにおいては、リスト内のオブジェクトを探索する際にもequalsメソッドが重要です。

下記のコードは、リストから特定のオブジェクトを探して取り出す一例です。

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("orange");
        list.add("banana");

        if (list.contains("orange")) {
            System.out.println("リストにorangeが存在します。");
        } else {
            System.out.println("リストにorangeが存在しません。");
        }
    }
}

このコードでは、ArrayListにフルーツの名前が格納されています。

containsメソッドで”orange”がリスト内に存在するかどうかを確認しています。このとき内部でequalsメソッドが使用されます。

実行すると、「リストにorangeが存在します。」と出力されます。

○サンプルコード12:マップのキーとしての使用

JavaのMapインターフェースにおいても、equalsメソッドはキーの比較に活用されます。

下記のコードはHashMapを用いた例です。

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("orange", 2);

        if (map.containsKey("apple")) {
            System.out.println("キーとしてappleが存在します。");
        } else {
            System.out.println("キーとしてappleが存在しません。");
        }
    }
}

このコードでcontainsKeyメソッドを用いると、指定したキー”apple”が存在するかどうかが確認できます。

こちらも内部でequalsメソッドが活用されます。

このコードを実行すると、「キーとしてappleが存在します。」と表示されます。

○サンプルコード13:equalsメソッドを使ったテストケース作成

単体テストを行う際にも、equalsメソッドは非常に有用です。

特にJUnitなどのテスティングフレームワークで、オブジェクトの状態を確認するために頻繁に用います。

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }
}

このコードでは、Calculatorクラスのaddメソッドが正しく動作するかをテストしています。

assertEqualsメソッドで期待値と計算結果が等しいかを確認します。

このときにも、内部でequalsメソッドが用いられています。

まとめ

この記事で扱ったJavaのequalsメソッドは、オブジェクト同士の等価性を確認する際に非常に重要な役割を果たします。

適切な使い方をすることで、ソフトウェア開発の多くの側面で効果を発揮します。

具体的には、基本的なString比較から独自クラスのオーバーライド、さらにはリストやマップでの探索、テストケースの作成まで多岐にわたります。

この記事が、Javaにおけるequalsメソッドの理解を深める一助になれば幸いです。

最後に、equalsメソッドは非常に強力なツールである一方で、その力を最大限に活用するためには細心の注意が必要であることを強調しておきます。

適切な知識と注意深い実装が求められるこのメソッドを、是非ともマスターして日々の開発作業に活かしてください。