hashCode()とequals()に関する質問への回答「再版」

この章の内容は主に次の質問に対処します。

1.equals  () は何をするのですか?

2equals  () と == の違いは何ですか?

hashCode() の機能は何ですか?

hashCode()とequals()の間にはどのような関係がありますか?

HashMap に関する面接でよくある質問5つ


パート 1 equals() の役割

quals() の機能は、  2 つのオブジェクトが等しいかどうかを判断することです。

equals()はJDKのObject.javaで定義されています。2 つのオブジェクトが等しいかどうかは、アドレスが等しいかどうか (つまり、同じオブジェクトであるかどうか) で判断します。ソースコードは次のとおりです。

public booleanquals(Object obj) {
    return (this == obj);
}

equals() メソッドは Object.java で定義されているため、すべての Java クラスが equals() メソッドを実装し、すべてのクラスが equals() を使用して 2 つのオブジェクトが等しいかどうかを比較できます。ただし、デフォルトの「 equals() 」メソッドの使用は「 == 」メソッドと同等であるとすでに述べましたしたがって、通常は、equals() メソッドを書き換えます。2 つのオブジェクトの内容が等しい場合、equals() メソッドは true を返し、そうでない場合は、fasle を返します。  

「クラスがequals()メソッドをオーバーライドするかどうか」により、2つのカテゴリに分けられます。
(01) クラスが、equals() メソッドをオーバーライドしない場合、equals() を通じて 2 つのオブジェクトを比較するとき、実際には、2 つのオブジェクトが同じオブジェクトであるかどうかが比較されます。このとき、2つのオブジェクトを「==」で比較することと同じになります。
(02) クラスのequals()メソッドをオーバーライドして、2つのオブジェクトが他の方法で等しいかどうかをequals()に比較させることができます。通常は、2 つのオブジェクトの内容が等しい場合、equals() メソッドは true を返し、そうでない場合は fasle を返します。


以下では、上記の 2 つの状況を例を挙げて説明します。

1. 「equals() メソッドはオーバーライドされません」

コードは次のとおりです (EqualsTest1.java)

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc equals()的测试程序。
 *
 * @author skywang
 * @emai [email protected]
 */
public class EqualsTest1{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }
    }
}

実行結果

間違い

結果分析:

       「p1 と p2 が等しいかどうかを比較する」ために p1.equals(p2) を渡します。実際には、Object.javaのequals()メソッドが呼び出されます、つまり、(p1==p2)が呼び出されます。「p1とp2が同じオブジェクトかどうか」を比較しています。
       p1 と p2 の定義から、それらは同じ内容を持っていますが、2 つの異なるオブジェクトであることがわかります。したがって、戻り結果は false になります。

2. 「equals()メソッドをオーバーライドする場合

上記のEqualsTest1.javaを変更し、equals() メソッドをオーバーライドします

コードは次のとおりです (EqualsTest2.java)

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc equals()的测试程序。
 *
 * @author skywang
 * @emai [email protected]
 */
public class EqualsTest2{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

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

実行結果

真実

結果分析:

EqualsTest2.java で Person の equals() 関数を書き直しました。2 つの Person オブジェクトの名前と年齢が等しい場合、true を返します。
したがって、演算結果は true を返します。

ちなみに、これについて言えば、Java のequals() の要件です。以下の点があります。

1. 対称性: x.equals(y) が「true」を返す場合、y.equals(x) も「true」を返す必要があります。
2. 反射: x.equals(x) は「true」を返さなければなりません。
3. 類推: x.equals(y) が "true" を返し、y.equals(z) が "true" を返す場合、z.equals(x) も "true" を返す必要があります。
4. 一貫性: x.equals(y) が "true" を返す場合、x と y の内容が変わらない限り、x.equals(y) を何度繰り返しても、戻り値は "true" になります。
5. 空でない x.equals(null) は常に "false" を返します; x.equals (x とは異なる型のオブジェクト) は常に "false" を返します。

ここで、equals() の役割を確認してみましょう。2 つのオブジェクトが等しいかどうかを判断します。equals() を書き直すときは、その関数を変更しないでください。


パート 2quals() と == の違いは何ですか?

== : その機能は、2 つのオブジェクトのアドレスが等しいかどうかを判断することです。つまり、2つのオブジェクトは同一のオブジェクトではないと判断される。

quals() : その機能は、2 つのオブジェクトが等しいかどうかを判断することでもあります。ただし、一般に 2 つの使用例があります (前のパート 1 で詳しく紹介されています)。
                 ケース 1、クラスは、equals() メソッドをオーバーライドしません。次に、equals() を使用してこのクラスの 2 つのオブジェクトを比較する場合、「==」を使用してこれら 2 つのオブジェクトを比較することと同じになります。
                 ケース 2、クラスは、equals() メソッドをオーバーライドします。一般に、equals() メソッドをオーバーライドして 2 つのオブジェクトの内容を等しくします; それらの内容が等しい場合は true を返します (つまり、2 つのオブジェクトが等しいと見なされます)。

以下に、それらの違いを例と比較します。

コードは次のとおりです。 

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc equals()的测试程序。
 *
 * @author skywang
 * @emai [email protected]
 */
public class EqualsTest3{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        System.out.printf("p1.equals(p2) : %s\n", p1.equals(p2));
        System.out.printf("p1==p2 : %s\n", p1==p2);
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

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

実行結果

p1.equals(p2) : true
p1==p2 : false

結果分析:

EqualsTest3.java の場合:
(01)  p1.equals(p2)
        p1 と p2 の内容が等しいかどうかを判定します。Person は、equals() メソッドをオーバーライドし、この equals() は、p1 と p2 の内容が等しいかどうかを判断するために使用され、p1 と p2 の内容が等しいため、true を返します。

(02)  p1==p2
       p1とp2が同じオブジェクトかどうかを判定します。これらは新しく作成された 2 つの Person オブジェクトであるため、false が返されます。


パート 3 hashCode() の役割

hashCode() の役割は、ハッシュ コード (ハッシュ コードとも呼ばれる) を取得することであり、実際には int 整数を返します。このハッシュ コードの機能は、ハッシュ テーブル内のオブジェクトのインデックス位置を決定することです。

hashCode() は JDK の Object.java で定義されています。つまり、Java のクラスには hashCode() 関数が含まれています。
        ただし、すべての Java クラスには hashCode() 関数が含まれています。ただし、「クラスのハッシュ テーブル」が作成および使用される場合にのみ (「ハッシュ テーブル」については以下の説明を参照)、このクラスの hashCode() が役立ちます (機能は次のとおりです)。他の場合(たとえば、クラスの単一オブジェクトの作成、クラスのオブジェクトの配列の作成など)、クラスの hashCode() は効果がありません。上記のハッシュ テーブル
       とは、本質的にハッシュ テーブルである Java コレクション内のクラス (HashMap、Hashtable、HashSet など) を指します。

       つまり、hashCode() はハッシュ テーブルでのみ有用であり、他の場合には役に立ちません。ハッシュ テーブル内の hashCode() の機能は、オブジェクトのハッシュ コードを取得し、ハッシュ テーブル内のオブジェクトの位置を決定することです。

わかりました!ここまでで、hashCode() の機能はハッシュ コードを取得することであることがわかりました。しかし、ハッシュコードは何のためにあるのでしょうか? ハッシュテーブルにハッシュコードが必要なのはなぜですか? これらの問題を解決するには、ハッシュ テーブルを理解する必要があります。ハッシュ テーブルの内容については、一言で説明してもよくわかりませんが、次の記事を参照してください。

【再録】ハッシュテーブル(Hash Table)理論から実践まで(on)

【再録】ハッシュテーブル(Hash Table)の理論から実践まで(in)

【再録】ハッシュテーブル(Hash Table)の理論から実践まで(後編)

以下の内容を理解していただくために、ハッシュコードの機能を簡単に紹介します。

ハッシュテーブルにはキーと値のペア(キーバリュー)が格納されており、その「キー」に応じて対応する「値」を素早く取り出すことができるのが特徴であることは皆さんご存知でしょう。ここでハッシュコードが使用されます。
ハッシュ テーブルの本質は配列を通じて実現されます。ハッシュ テーブルの「値」を取得したいとき、実際には配列内の特定の位置にある要素を取得したいのです。配列の位置は「キー」によって取得され、さらに「キー」に対応するハッシュコードによって配列の位置が計算されます。

以下では、HashSet を例として、hashCode() の機能を詳しく説明します。

        HashSet にすでに 1000 個の要素があると仮定します。1001番目の要素を挿入する場合はどうすればよいですか? HashSet は Set コレクションであるため、要素の重複は許可されません。
        「1001 番目の要素を 1 つずつ前の 1000 個の要素と比較します」? 明らかに、この効率はかなり低いです。ハッシュ テーブルはこの問題をうまく解決し、要素のハッシュ コードに従ってハッシュ テーブル内の要素の位置を計算し、その位置に要素を挿入します。同じ要素の場合、当然 1 つだけが保存されます。
        2 つの要素が等しい場合、それらのハッシュ コードも等しくなければならないことがわかりますが、その逆は必ずしも真ではありません。ハッシュ テーブルでは、
                           1. 2 つのオブジェクトが等しい場合、それらの hashCode() 値は同じでなければなりません;
                           2. 2 つのオブジェクトの hashCode() が等しい場合、それらは必ずしも等しいとは限りません。

                           注: これはハッシュ テーブルの場合に当てはまります。非ハッシュテーブルでは true でなければなりません。

2 つのオブジェクトを比較するには、書き換えられたequals()を使用します。すべてのequals()とhashCode()には次の関係があります。

「hashCode()の役割」については以上です。


パート4 hashCode()とequals()の関係

次に、別のトピックについて説明します。インターネット上の多くの記事では、hashCode() と同等のものが関連付けられていますが、その一部は徹底されておらず、読者に誤解を招く恐れがあると思われます。ここで「hashCode()とequals()の関係」を自分なりに整理してみました。

「クラスの目的」を使って「hashCode()とequals()の関係」を2つの場合に分けて説明します。

1. 1つ目のタイプは「クラスに対応したハッシュテーブル」を作成しません

         ここで「クラスに対応するハッシュ テーブルを作成しない」とは、本質的にハッシュ テーブルである HashSet、Hashtable、HashMap などのデータ構造ではこのクラスを使用しないことを意味します。たとえば、このクラスの HashSet コレクションは作成されません。

        この場合、このクラスの「hashCode()」と「equals()」は互いに何の関係もありません。
        この場合、equals() を使用して、クラスの 2 つのオブジェクトが等しいかどうかを比較します。また、 hashCode() はまったく効果がないため、 hashCode() を無視してください。

以下では、クラスの 2 つのオブジェクトが等しい か どうかの場合の hashCode() の値を確認する例を使用します。

ソースコードは次のとおりです (NormalHashCodeTest.java)。

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
 *
 * @author skywang
 * @emai [email protected]
 */
public class NormalHashCodeTest{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc 覆盖equals方法
         */
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

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

実行結果

p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)

結果からもわかります。p1と p2 が等しい場合、hashCode() は必ずしも等しいわけではありません。

2. 2つ目は「クラスに対応したハッシュテーブル」を作成します

        ここで言う「クラスに対応したハッシュテーブル」とは、本質的にハッシュテーブルであるデータ構造であるHashSet、Hashtable、HashMapなどでこのクラスを使用することを意味します。たとえば、このクラスの HashSet コレクションが作成されます。

        この場合、このクラスの「hashCode()」と「equals()」は関連しています:
        1) 2 つのオブジェクトが等しい場合、それらの hashCode() 値は同じでなければなりません。
              ここでの等価性とは、equals() を通じて 2 つのオブジェクトを比較するときに true を返すことを指します。
        2) 2 つのオブジェクトの hashCode() が等しい場合、それらは必ずしも等しいとは限りません。
               ハッシュ テーブルでは hashCode() が等しい、つまり 2 つのキーと値のペアのハッシュ値が等しいためです。ただし、ハッシュ値が等しい場合でも、キーと値のペアが等しいとは限りません。「2 つの異なるキーと値のペア、ハッシュ値は等しい」という文を追加します。これはハッシュの衝突です。

        また、この場合。2 つのオブジェクトが等しいかどうかを判断するには、equals() をオーバーライドするだけでなく、hashCode() 関数もオーバーライドする必要があります。それ以外の場合、equals() は効果がありません。
たとえば、PERSON クラスの HashSet コレクションを作成するには、PERSON クラスの equals() メソッドと hashCode() メソッドを同時にオーバーライドする必要があります。
        単にequals()メソッドをオーバーライドする場合。equals() メソッドでは必要な効果が得られないことがわかります。

参照コード (ConflictHashCodeTest1.java):

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
 *
 * @author skywang
 * @emai [email protected]
 */
public class ConflictHashCodeTest1{

    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);

        // 新建HashSet对象
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // 比较p1 和 p2, 并打印它们的hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // 打印set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return "("+name + ", " +age+")";
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

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

実行結果

p1.equals(p2) : true; p1(1169863946) p2(1690552137)
セット:[(eee, 100), (eee, 100), (aaa, 200)]

結果分析:

        パーソンのequals()をオーバーライドします。しかし、非常に奇妙な発見がありました。HashSet にはまだ重複した要素、p1 と p2 が存在します。なぜこのようなことが起こるのでしょうか?

        これは、p1 と p2 の内容が等しいにもかかわらず、それらの hashCode() が等しくないため、HashSet が p1 と p2 を追加すると、それらは等しくないとみなされるためです。

以下では、equals() メソッドと hashCode() メソッドの両方をオーバーライドします。

参照コード (ConflictHashCodeTest2.java):

 コードを表示

import java.util.*;
import java.lang.Comparable;

/**
 * @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
 *
 * @author skywang
 * @emai [email protected]
 */
public class ConflictHashCodeTest2{

    public static void main(String[] args) {
        // 新建Person对象,
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        Person p3 = new Person("aaa", 200);
        Person p4 = new Person("EEE", 100);

        // 新建HashSet对象
        HashSet set = new HashSet();
        set.add(p1);
        set.add(p2);
        set.add(p3);

        // 比较p1 和 p2, 并打印它们的hashCode()
        System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
        // 比较p1 和 p4, 并打印它们的hashCode()
        System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
        // 打印set
        System.out.printf("set:%s\n", set);
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc重写hashCode
         */
        @Override
        public int hashCode(){
            int nameHash =  name.toUpperCase().hashCode();
            return nameHash ^ age;
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

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

実行結果

p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
セット:[aaa - 200、eee - 100]

結果分析:

        これで、equals() が有効になり、HashSet 内に重複する要素がなくなりました。
        p1 と p2 を比較すると、 hashCode() が等しいことがわかり、equals() を介して比較すると true が返されます。したがって、p1 と p2 は等しいとみなされます。
        p1 と p4 を比較すると、 hashCode() が等しいにもかかわらず、equals() を介して比較すると false が返されることがわかりました。したがって、p1 と p4 は等しくないものとみなされます。

面接の質問

 1つ

二、 

おすすめ

転載: blog.csdn.net/weixin_61061381/article/details/125827945