1. 概要

このチュートリアルでは、Javaの2つの基本的な同等性チェック(参照の同等性と値の同等性)について説明します。 それらを比較し、例を示し、それらの主な違いを強調します。

また、 null チェックに焦点を当て、オブジェクトを操作するときに値の同等性ではなく参照の同等性を使用する必要がある理由を理解します。

2. 参照の等式

等式演算子( == )で表される参照比較を理解することから始めます。 参照の同等性は、2つの参照がメモリ内の同じオブジェクトを指している場合に発生します。

2.1. プリミティブ型の等式演算子

Java プリミティブ型は、単純な非クラスの生の値であることがわかっています。 プリミティブ型で等式演算子を使用する場合、それらの値を比較しているだけです。

int a = 10;
int b = 15;
assertFalse(a == b);

int c = 10;
assertTrue(a == c);

int d = a;
assertTrue(a == d);

上に示したように、の等価性と参照チェックはプリミティブに対して同じように機能します。 同じ値で新しいプリミティブを初期化すると、チェックは trueを返します。さらに、元の値を新しい変数に再割り当てして比較すると、演算子は同じ結果を返します。

nullチェックを実行してみましょう。

int e = null; // compilation error
assertFalse(a == null); // compilation error
assertFalse(10 == null); // compilation error

Javaは、nullをプリミティブに割り当てることを禁止します。 一般に、プリミティブ変数または値に対して等式演算子を使用してnullチェックを実行することはできません。

2.2. オブジェクト型の等式演算子

Java オブジェクト型については、等式演算子は、オブジェクト値を無視して、参照等式比較のみを実行します。 テストを実装する前に、簡単なカスタムクラスを作成しましょう。

public class Person {
    private String name;
    private int age;

    // constructor, getters, setters...
}

それでは、いくつかのクラスオブジェクトを初期化し、等式演算子の結果を調べてみましょう。

Person a = new Person("Bob", 20);
Person b = new Person("Mike", 40);
assertFalse(a == b);

Person c = new Person("Bob", 20);
assertFalse(a == c);

Person d = a;
assertTrue(a == d);

結果は以前とはかなり異なります。 2番目のチェックでは、プリミティブに対して true を取得しているときに、falseが返されます。 前に述べたように、等式演算子は比較時にオブジェクトの内部値を無視します。 は、2つの変数が同じメモリアドレスを参照していることを確認するだけです。

プリミティブとは異なり、オブジェクトの操作中にnullを使用できます。

assertFalse(a == null);
Person e = null;
assertTrue(e == null);

等式演算子を使用してnullを比較することにより、変数に割り当てられたオブジェクトがすでに初期化されているかどうかを確認します

3. 価値の平等

次に、値の同等性テストに焦点を当てましょう。 値の同等性は、2つの別々のオブジェクトがたまたま同じ値または状態を持っている場合に発生します。

これは値を比較し、オブジェクトのequals()メソッドと密接に関連しています。 前と同じように、その使用法をプリミティブおよびオブジェクトタイプと比較して、主な違いを見てみましょう。

3.1. equals()プリミティブ型のメソッド

ご存知のように、プリミティブは単一の値を持つ基本型であり、メソッドを実装していません。 したがって、プリミティブを使用してequals()メソッドを直接呼び出すことはできません。

int a = 10;
assertTrue(a.equals(10)); // compilation error

ただし、すべてのプリミティブには独自のラッパークラスがあるため、ボクシングメカニズムを使用してオブジェクト表現にキャストできます。 次に、オブジェクトタイプを使用しているかのように、 equals()メソッドを簡単に呼び出すことができます。

int a = 10;
Integer b = a;

assertTrue(b.equals(10));

3.2. equals()オブジェクト型のメソッド

Personクラスに戻りましょう。 equals()メソッドが正しく機能するには、クラスに含まれるフィールドを考慮して、カスタムクラスのメソッドをオーバーライドする必要があります。

public class Person {
    // other fields and methods omitted

    @Override
    public boolean equals(Object o) {
        if (this == o) 
            return true;
        if (o == null || getClass() != o.getClass()) 
            return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }
}

まず、 equals()メソッドは、指定された値が同じ参照を持っている場合に true を返します。これは、参照演算子によってチェックされます。 そうでない場合は、同等性テストを開始します。

さらに、両方の値についてクラスオブジェクトの同等性をテストします。 異なる場合はfalseを返します。 それ以外の場合は、同等性のチェックを続行します。 最後に、各プロパティを個別に比較した結果を組み合わせて返します。

それでは、前のテストを変更して結果を確認しましょう。

Person a = new Person("Bob", 20);
Person b = new Person("Mike", 40);
assertFalse(a.equals(b));

Person c = new Person("Bob", 20);
assertTrue(a.equals(c));

Person d = a;
assertTrue(a.equals(d));

ご覧のとおり、2番目のチェックでは、参照の等式ではなく、trueが返されます。 オーバーライドされたequals()メソッドは、オブジェクトの内部値を比較します。

equals()メソッドをオーバーライドしない場合、親クラスObjectのメソッドが使用されます。 Object.equals()メソッドは参照の等価性チェックのみを行うため、Personオブジェクトを比較するときに期待する動作とは異なる場合があります。

上記のhashCode()メソッドは示していませんが、 equals()メソッド これらのメソッド間の一貫性を確保するため

4. ヌルの平等

最後に、 equals()メソッドがnull値でどのように機能するかを確認しましょう。

Person a = new Person("Bob", 20);
Person e = null;
assertFalse(a.equals(e));
assertThrows(NullPointerException.class, () -> e.equals(a));

equals()メソッドを使用して他のオブジェクトに対してチェックすると、これらの変数の順序に応じて2つの異なる結果が得られます。 null参照でequals()メソッドを呼び出すため、最後のステートメントは例外をスローします。 最後のステートメントを修正するには、最初に等価演算子チェックを呼び出す必要があります。

assertFalse(e != null && e.equals(a));

これで、条件の左側が false を返し、ステートメント全体が false と等しくなり、NullPointerExceptionがスローされなくなります。 したがって、最初にequals()メソッドを呼び出している値がnull でないことを確認する必要があります。そうでない場合、厄介なバグが発生する可能性があります。

さらに、Java 7以降、nullセーフな Objects#equals() static メソッドを使用して、同等性チェックを実行できます。

assertFalse(Objects.equals(e, a));
assertTrue(Objects.equals(null, e));

このヘルパーメソッドは、 NullPointerException がスローされないように追加のチェックを実行し、両方のパラメーターがnullの場合にtrueを返します。

5. 結論

この記事では、参照の同等性と値の同等性がプリミティブ値とオブジェクト値に対してチェックする方法について説明しました。

参照の同等性をテストするには、==演算子を使用します。 この演算子は、プリミティブ値とオブジェクトでは少し異なる動作をします。 プリミティブで等式演算子を使用すると、値が比較されます。 一方、オブジェクトに使用する場合は、メモリ参照をチェックします。 それをと比較することによってヌル値の場合、オブジェクトがメモリ内で初期化されていることを確認するだけです。

Javaで値の同等性テストを実行するには、 Objectから継承されたequals()メソッドを使用します。 プリミティブは単純な非クラス値であるため、このメソッドはラップせずに呼び出すことはできません。

また、インスタンス化されたオブジェクトに対して equals()メソッドのみを呼び出すことを忘れないでください。 それ以外の場合は、例外がスローされます。 これを防ぐには、 null 値が疑われる場合は、==演算子を使用して値を確認する必要があります。

いつものように、例のソースコードはGitHubから入手できます。