ホームページ Java &#&チュートリアル なぜequals()メソッドをオーバーライドする必要があるのでしょうか?

なぜequals()メソッドをオーバーライドする必要があるのでしょうか?

Jun 29, 2017 am 09:58 AM
equals java

equals() メソッドを書き直す必要があるのはなぜですか?

クラスのメンバー変数に基づいて2つのクラスのインスタンスが等しいかどうかを判断するなど、2つのオブジェクトが論理的に等しいかどうかを判断します。Objectのequalsメソッドは、2つの参照変数が同じオブジェクトであるかどうかのみ判断できます。このように、多くの場合、equals() メソッドをオーバーライドする必要があります。

重複オブジェクトのないコレクションに要素を追加する場合、多くの場合、コレクション内に既知のオブジェクトがあるかどうかを最初に判断する必要があるため、equals メソッドをオーバーライドする必要があります。

equals() メソッドをオーバーライドするにはどうすればよいですか?

equals メソッドをオーバーライドするための要件:

1. 再帰性: null 以外の参照 x に対して、x.equals(x) は true を返す必要があります。

2. 対称性: x と y を参照する場合、x.equals(y) が true を返す場合、y.equals(x) も true を返す必要があります。

3. 推移性: 任意の参照 x、y、z について、x.equals(y) が true を返し、y.equals(z) が true を返す場合、x.equals(z) も true を返す必要があります。

4. 一貫性: x と y によって参照されるオブジェクトが変更されていない場合、x.equals(y) を繰り返し呼び出しても同じ結果が返されるはずです。

5. null でない可能性: null 以外の参照 x の場合、x.equals(null) は false を返す必要があります。

1. 再帰性の原則

JavaBeans では、実際のビジネス状況に基づいて 2 つのオブジェクトが等しいかどうかを判断するために、equals メソッドがオーバーライドされることがよくあります。たとえば、person クラスを作成し、その名前に基づいて 2 人の人物を判断します。クラス インスタンス オブジェクトが等しいかどうか。コードは次のとおりです。

 1 public class Person { 2     private String name; 3  4     public Person(String name) { 5         this.name = name; 6     } 7  8     public String getName() { 9         return name;10     }11 12     public void setName(String name) {13         this.name = name;14     }15 16     @Override17     public boolean equals(Object obj) {18         if (obj instanceof Person) {19             Person person = (Person) obj;20             return name.equalsIgnoreCase(person.getName().trim());21         }22         return false;23     }24 25     public static void main(String[] args) {26         Person p1 = new Person("张三");27         Person p2 = new Person("张三    ");28         List<Person> list = new ArrayList<Person>();29         list.add(p1);30         list.add(p2);31         System.out.println("是否包含张三:" + list.contains(p1));32         System.out.println("是否包含张三:" + list.contains(p2));33     }34 }
ログイン後にコピー

list には生成された person オブジェクトが含まれており、結果は true になるはずですが、実際の結果は次のとおりです。ここでは文字列スペースの問題が考慮され、先頭と末尾のスペースが削除されます。

Zhang San が含まれるかどうか: true

Zhang San が含まれるかどうか: false

2 番目が false なのはなぜですか?

その理由は、リストに要素が含まれているかどうかをチェックするときに、オブジェクトのequalsメソッドを呼び出すことによって判断されるためです。つまり、contains(p2)を渡すと、p2.equals(p1)とp2.equalsが実行されます。 (p2) は true を返す限り順番に実行され、結果は true になります。しかし、ここで p2.equals(p2) は false を返しますか?文字の前後のスペースをカットしているため、p2.equals(p2) の比較は実際には次のようになります: "Zhang San".equals ("Zhang San") 一方にはスペースがあり、もう一方にはスペースがありません。これはエラーです。

これは、等号の再帰原則に違反します。null 以外の参照 x に対して、x.equals(x) は true を返す必要があります。

これは、trim メソッドを削除することで解決できます。

2. 対称性の原則

上記の例は、NULL 値を渡すとどうなるでしょうか。ステートメントを追加します: Person p2=new Person(null);

Result:

是否包含张三:trueException in thread "main" java.lang.NullPointerException//空指针异常
ログイン後にコピー

原因 p2.equals(p1) を実行すると、p2 の名前が null 値であるため、name.equalsIgnoreCase() メソッドが呼び出された null ポインタ例外が報告されます。

これは、equals メソッドをオーバーライドするときの対称性の原則に従っていません。x、y が適用されるあらゆる状況で、x.equals(y) が true を返したい場合は、y.equals(x) も true を返す必要があります。 。

equals メソッドに値が null かどうかの判断を追加する必要があります:

 1 @Override 2     public boolean equals(Object obj) { 3         if (obj instanceof Person) { 4             Person person= (Person) obj; 5             if (person.getName() == null || name == null) { 6                 return false; 7             }else{ 8                 return name.equalsIgnoreCase(person.getName()); 9             }10         }11         return false;12     }
ログイン後にコピー

3. 推移性の原則

これで、person クラスを継承する Employee クラスができました:

 1 public class Employee extends Person{ 2     private int id; 3    4    5     public int getId() { 6         return id; 7     } 8     public void setId(int id) { 9         this.id = id;10     }11     public Employee(String name,int id) {12         super(name);13         this.id = id;14         // TODO Auto-generated constructor stub15     }16     @Override17     public boolean equals(Object obj) {18         if(obj instanceof Employee){19             Employee e = (Employee)obj;20             return super.equals(obj) && e.getId() == id;21         }22         return super.equals(obj);23     }24   25     public static void main(String[] args){26         Employee e1=new Employee("张三",12);27         Employee e2=new Employee("张三",123);28         Person p1 = new Person("张三");29   30         System.out.println(p1.equals(e1));31         System.out.println(p1.equals(e2));32         System.out.println(e1.equals(e2));33     }34 }
ログイン後にコピー

Only名前と ID 全員が同じ場合にのみ同じ従業員と見なされます。同じ名前と姓を持つことは避けてください。 main では、2 人の従業員とソーシャルワーカーは、同姓同名であっても、決して同一人物ではないと定義されています。実行結果は 3 つすべてが false であるはずです。しかし:

tru​​e

tru​​e

false

p1 は確実に e1 および e2 と等しくなります。同じクラスのインスタンスも等しくありませんか?

p1.equals(e1)は親クラスのequalsメソッドを呼び出してe1がpersonのインスタンスであるかどうかを判定しているので、結果はtrueとなります。しかし、e1 と e2 が p1 に等しくないということは当てはまりません。これも対称性の原理に違反する典型的なケースです。

e1 は e2 と等しくないですか?

equals(e2)は、Employeeのequalsメソッドを呼び出します。これは、名前が同じであるだけでなく、ジョブ番号も同じであると判断し、両者のジョブ番号が異なる場合は正しいと判断します。等しい。ただし、p1 は e1 に等しく、e2 にも等しいが、e1 は e2 に等しくありません。この方程式が推移的ではない理由は、たとえばオブジェクト x などの推移性の原則に違反しているためです。 , y, z; x.equals(y) が true を返し、y.equals(z) が true を返す場合、x.equals(z) も true を返す必要があります。

上記の状況は、親クラスが、instanceof キーワード (この特定のクラスのインスタンスであるか、そのサブクラスのインスタンスであるか) を使用して、クラスのインスタンス オブジェクトであるかどうかを判断するために発生します。これにより、サブクラスが「抜け穴を悪用する」ことが容易になります。 。」

解決策は非常に簡単です。getClass を使用して、person クラスの equals メソッドを次のように変更します。

4、必须覆写hashCode方法这样结果就是三个false。

覆写equals方法就必须覆写hashCode方法,这是Javaer都知道的。

原因就是HashMap的底层处理机制是以数组的方式保存map条目的,这其中的关键是这个数组下标的处理机制:

依据传入元素的hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到map条目的链表中。同理检查键是否存在也是根据哈希吗确定文职,然后遍历查找键值的。

那么对象的hashCode方法返回的是什么呢?

他是一个对象的哈希码,是有Object类的本地方法生成的,确保每个对象有一个哈希码。

1、重写equals方法实例   部分代码参考

重写equals方法的目的是判断两个对象的内容(内容可以有很多,比如同时比较姓名和年龄,同时相同的才是用一个对象)是否相同。

如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。特别指出利用equals比较八大包装对象,(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址。

package com.lk.C;class User {private String name;private int age;public int getAge() {return age;
    }public void setAge(int age) {this.age = age;
    }public void setName(String name) {  this.name = name;  
    }public String getName() {  return name;  
    }public boolean equals(Object obj) {  if(this == obj) {  return true;  
        }  if(null == obj) {  return false;  
        }  if(this.getClass() != obj.getClass()) {  return false;  
        }  

        User user = (User) obj;  if(this.name.equals(user.name)&&this.age == user.age) {  return true;  
        }  return false;  
    }  
    
}  

public class Test6 {  public static void main(String[] args) {  
        User userA = new User();  
        userA.setName("王明");
        userA.setAge(10);

        User userB = new User();  
        userB.setName("王明");
        userB.setAge(10);

        User userC = new User();  
        userC.setName("王亮");
        userC.setAge(10);

        System.out.println("userA equals userB:" + userA.equals(userB));  
        System.out.println("userA equals userC:" + userA.equals(userC));
    }  
}
ログイン後にコピー
userA equals userB:trueuserA equals userC:false
ログイン後にコピー

在Java中,问什么说重写了equals方法都要进而重写Hashcode方法呢?

原因如下:当equals此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:

(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true

(2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false

hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。

这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致。

2、看看下面的三段程序

package com.lk.C;public class Test7 {public static void main(String[] args) {int a = 10;int b = 10;
        System.out.print("基本类型a==b:");
        System.out.println(a == b);
        System.out.println("-----");
        
        String s1 = "abc";
        String s2 = "abc";
        System.out.print("String类型是s1==s2:");
        System.out.println(s1 == s2);
        System.out.println("-----");
        
        String s3 = new String("abc");
        String s4 = new String("abc");//可以看出==比较的是栈的地址是否相同System.out.print("String类型用new String()是s1==s2:");
        System.out.println(s3 == s4);
        System.out.println(s1 == s3);
        System.out.println("-----");
        
        Integer i1 = 1;
        Integer i2 = 1;
        System.out.print("包装类型是i1==i2:");
        System.out.println(i1 == i2);
        System.out.println("-----");
        
        Integer i3 = 128;
        Integer i4 = 128;//此时输出false是因为Integer在-128-127之间会缓存,超出这个范围就不会缓存了System.out.print("包装类型是i3==i4:");
        System.out.println(i3 == i4);
        System.out.println("-----");
        
        Integer i5 = new Integer("1");
        Integer i6 = new Integer("1");
        System.out.print("包装类型用new Integer()是i5==i6:");
        System.out.println(i5 == i6);//用new Integer()多少都不会缓存System.out.println("-----");
        
        A a1 = new A(1);
        A a2 = new A(1);
        A a3 = a2;
        System.out.print("普通引用类型a1 == a2:");
        System.out.println(a1 == a2);
        System.out.println(a2 == a3);//对象赋给新对象连地址都是相同的System.out.println("-----");
    }
}class A{int i;public A(int i){this.i = i;
    }
}
ログイン後にコピー
基本类型a==b:true-----String类型是s1==s2:true-----String类型用new String()是s1==s2:falsefalse-----包装类型是i1==i2:true-----包装类型是i3==i4:false-----包装类型用new Integer()是i5==i6:false-----普通引用类型a1 == a2:falsetrue-----
ログイン後にコピー
package com.lk.C;public class Test8 {public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println("基本类型没有equals方法");
        System.out.println("-----");
        
        String s1 = "abc";
        String s2 = "abc";
        System.out.print("String类型的equals方法:");
        System.out.println(s1.equals(s2));
        System.out.println("-----");
        
        String s3 = new String("abc");
        String s4 = new String("abc");//可以看出比较equals方法比较的是堆里的值是否相同System.out.print("String类型的new String()的equals方法:");
        System.out.println(s3.equals(s4));
        System.out.println("-----");
        
        System.out.print("String用==赋值和用new String()赋值的比较:");
        System.out.println(s1.equals(s3));
        System.out.println("-----");
        
        Integer i1 = 1;
        Integer i2 = 1;
        System.out.print("包装类的equals方法:");
        System.out.println(i1.equals(i2));
        System.out.println("-----");
        
        Integer i3 = new Integer(1);
        Integer i4 = new Integer(1);
        System.out.print("包装类的new Integer()用equals方法:");
        System.out.println(i3.equals(i4));
        System.out.println("-----");
        
        System.out.print("Integer用==赋值和用new Integer()赋值的比较:");
        System.out.println(i1.equals(i3));
        System.out.println("-----");
    }

}
ログイン後にコピー
基本类型没有equals方法-----String类型的equals方法:true-----String类型的new String()的equals方法:true-----String用==赋值和用new String()赋值的比较:true-----包装类的equals方法:true-----包装类的new Integer()用equals方法:true-----Integer用==赋值和用new Integer()赋值的比较:true-----
ログイン後にコピー
package com.lk.C;public class Test9 {public static void main(String[] args) {// TODO Auto-generated method stubStudent s1 = new Student("阿坤",21);
        Student s2 = new Student("阿坤",21);
        Student s3 = new Student();
        Student s4 = new Student();
        Student s5 = s1;
        System.out.print("普通类对象的==非默认构造:");
        System.out.println(s1 == s2);
        System.out.println(s1 == s5);
        System.out.println("-----");
        
        System.out.print("普通类对象的equals非默认构造:");
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s5));
        System.out.println("-----");
        
        System.out.print("普通类对象的==默认构造:");
        System.out.println(s3 == s4);
        System.out.println("-----");
        
        System.out.print("普通类对象的equals默认构造:");
        System.out.println(s3.equals(s4));
        System.out.println("-----");
        
        System.out.print("对普通对象的属性进行比较equals:");
        System.out.println(s1.name.equals(s2.name));
        System.out.print("对普通对象的属性进行比较==:");
        System.out.println(s1.name == s2.name);
    }

}class Student{public String name;public int age;public Student(){
        
    }public Student(String name,int age){this.name = name;this.age = age;
    }public void test(){
        System.out.println(this.name);
        System.out.println(this.age);
    }
}
ログイン後にコピー
普通类对象的==非默认构造:falsetrue-----普通类对象的equals非默认构造:falsetrue-----普通类对象的==默认构造:false-----普通类对象的equals默认构造:false-----对普通对象的属性进行比较equals:true对普通对象的属性进行比较==:true
ログイン後にコピー

从以上的三个程序可以看出:

1)对于==:在简单类型中(int等),这能使用该方法进行比较,这种类型没有equals方法,int的值是存在栈中的,==比较的是栈的内容是否相同。在String类型中,比较特殊,用String=“”;这种进行赋值时,两个相同的值用==比较也是相同的。但是用new String(),赋值就不相同。说明String=“”时,java会检查在堆中是否由相同的值,如果有,把新对象的地址也同老对象的地址赋为相同,因此==比较会相同。但是new String()开辟的就是两个栈,因此用==比较不会相同。对于包装类,如Integer=“”;时,在-128-127会有缓存,请看上面程序。其他的情况与String类似。

2)对于equals:当时String类型或者是包装类,如Integer时,比较的就是堆中的值,Integer也无缓存之说。对于普通类,equals比较的内存的首地址,这时候和==是一样的,即比较两边指向的是不是同一个对象。详细请见程序三。

很好,很详细的文章,感谢网友的分享,记录下来只为学习。

以上程序都是亲自测试过。希望能对大家有帮助。

以下是一些在百度中找到的说法:

java中,
(1)对于字符串变量来说,equal比较的两边对象的内容,所以内容相同返回的是true。
至于你没问到的“==”,比较的是内存中的首地址,所以如果不是同一个对象,“==”不会返回true 而是false。
举个简单的例子,
String s1="abc", s2="abc";
String s3 =new String("abc");
String s4=new String("abc");
s1==s2 //true,s1.equals(s2) //true,s3.equals(s3) //true,equal比较的是内容s3==s4//false,==比较的是首地址,所以是false(2)对于非字符串变量,equals比较的内存的首地址,这时候和==是一样的,即比较两边指向的是不是同一个对象,
即
Sample sa1 = new Sample();
Sample sa2 = new Sample();
sa1.equals(sa2) //false,因为不是同一对象 注意,如果加上
sa1=sa2;
那么
sa1.equals(sa2) //true
ログイン後にコピー

以上がなぜequals()メソッドをオーバーライドする必要があるのでしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの平方根 Javaの平方根 Aug 30, 2024 pm 04:26 PM

Java の平方根のガイド。ここでは、Java で平方根がどのように機能するかを、例とそのコード実装をそれぞれ示して説明します。

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

Javaのアームストロング数 Javaのアームストロング数 Aug 30, 2024 pm 04:26 PM

Java のアームストロング番号に関するガイド。ここでは、Java でのアームストロング数の概要とコードの一部について説明します。

Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

See all articles