Why should the equals() method be rewritten?
Judge whether two objects are logically equal, such as judging whether instances of two classes are equal based on the member variables of the class, and The equals method in inherited Object can only judge two Whether the reference variable is the same object. In this way we often need to override the equals() method.
When we add elements to a collection without duplicate objects, objects are often stored in the collection. We need to first determine whether there are known objects in the collection, so we must override the equals method.
How to override the equals() method?
Requirements for overriding the equals method:
1. Reflexivity: For any non-null reference x, x.equals(x) should return true.
2. Symmetry: For any reference to x and y, if x.equals(y) returns true, then y.equals(x) should also return true.
3. Transitivity: For any reference x, y and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should also return true .
4. Consistency: If the objects referenced by x and y have not changed, then calling x.equals(y) repeatedly should return the same result.
5. Non-nullability: For any non-null reference x, x.equals(null) should return false.
1. Principle of reflexivity
In JavaBean, the equals method is often overridden to determine whether two objects are equal based on actual business conditions. For example, we Write a person class to determine whether two person class instance objects are equal based on their names. The code is as follows:
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 contains this generated person object, and the result should be true, but the actual result: the problem of string spaces is considered here, and the leading and trailing spaces are removed.
Whether Zhang San is included: true
Whether Zhang San is included: false
Why is the second one false?
The reason is that when checking whether the list contains elements, it is judged by calling the equals method of the object. That is to say, if contains(p2) is passed in, p2.equals(p1), p2.equals(p2) will be executed in sequence. ), as long as one returns true, the result is true. But here p2.equals(p2) returns false? Since we cut spaces before and after the characters, the comparison of p2.equals(p2) is actually: "Zhang San".equals ("Zhang San"). One has spaces and the other has no spaces, which is an error.
This violates the reflexive principle of equals: for any non-null reference x, x.equals(x) should return true.
This can be solved by removing the trim method.
2. Principle of symmetry
The above example is not very good. What will happen if we pass in a null value? Add a statement: Person p2=new Person(null);
Result:
是否包含张三:trueException in thread "main" java.lang.NullPointerException//空指针异常
The reason is that when p2.equals(p1) is executed, the name of p2 is A null value, so a null pointer exception will be reported when the name.equalsIgnoreCase() method is called.
This is not following the principle of symmetry when overriding the equals method: for any situation where x, y is applied, if you want x.equals(y) to return true, then y.equals(x) should also Return true.
The judgment of whether the value is null should be added to the equals method:
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. Transitivity principle
Now We have an Employee class that inherits from the person class:
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 }
It is the same employee only if the name and ID are the same, avoid having the same name and surname. It is defined in main that two employees and a social worker, although they have the same name and surname, are definitely not the same person. The running results should be all three are false. However:
true
true
false
p1 is definitely equal to e1 and e2. Aren’t instances of the same class also equal?
Because p1.equals(e1) calls the equals method of the parent class for judgment. It uses the instanceof keyword to check whether e1 is an instance of person. Since employee and person are inheritance relationships, the result is true. But it doesn’t hold true if you put it aside. e1 and e2 are not equal to p1. This is also a typical case of violating the principle of symmetry.
e1 is not equal to e2?
e1.equals(e2) calls the equals method of Employee. It not only determines that the names are the same but also the job numbers are the same. The job numbers of the two are different and it is correct when they are not equal. However, p1 is equal to e1 and is also equal to e2, but e1 is not equal to e2. There is a contradiction here. The reason why the equation is not transitive is that it violates the transitivity principle of equals: for instance objects x, y, z; if x.equals(y) Returns true, y.equals(z) returns true, then x.equals(z) should also return true.
The above situation occurs because the parent class uses the instanceof keyword (whether it is an instance of this specific class or its subclass) to determine whether it is an instance object of a class. This is very easy. Let subclasses "take advantage of loopholes."
The solution is very simple. Use getClass to determine the type. The equals method of the person class is modified as follows:
1 @Override 2 public boolean equals(Object obj) { 3 if (obj != null && obj.getClass() == this.getClass()) { 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 }
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
The above is the detailed content of Why should the equals() method be overridden?. For more information, please follow other related articles on the PHP Chinese website!