一次性看懂Equals和HashCode的用法与注意事项
一直对Equals和HashCode的概念及用法很是模糊,现在有时间,搜集了一下资料,进行了归纳总结。
关于Equals
Object类中默认的实现方式是 : return this == obj 。那就是说,只有this 和 obj引用同一个对象,才会返回true。
而我们往往需要用equals来判断 2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals,当然重写equals的同时最好重写对应的hashCode,尤其在堆散列存储结构,像hashSet,HashMap,HashTable等这些相关操作的时候,则java明确规定了重写equal必须重写hashCode,下面会对这块进行相关说明。
Java语言规范要求equals需要具有如下的特性:
自反性:对于任何非空引用 x,x.equals(x) 应该返回 true。
对称性:对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 true。
传递性:对于任何引用 x、y 和 z,如果 x.equals(y)返回 true,y.equals(z) 也应返回同样的结果。
一致性:如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果。
对于任意非空引用 x,x.equals(null) 应该返回 false。
重写equals方法时要遵循以上规范,例如:如下用法
public boolean equals(Object obj) {
if (this == obj) return true;
if ((obj==null)||(this.getClass()!=obj.getClass()))
return false;
User user = (User) obj;
return this.id==user.id&&this.name.equals(u ser.name);
}
关于HashCode
关于HashCode的作用及注意事项,看完这篇完整就够了,我觉得看了后觉得这个博主写的很到位,推荐看一下:
https://blog.csdn.net/lijiecao0226/article/details/24609559
正确重写HashCode
参考文档:https://blog.csdn.net/benjaminzhang666/article/details/9468605#
注意:重写HashCode的时候不能重写成相同的值,对于两个不相同的对象(这里指内部成员属性值不相同),那么就没必要比较其equals,应该在重写HashCode的时候都把尽量把成员属性都计算在内好点,这样在进行散列存储结构的集合操作的时候就不用去计算其equals,直接根据HashCode就可以判断是否将对象加入到集合中,当然对于成员较多的对象HashCode则越复杂,也会影响性能
下面给出例子说明
重写HashCode时返回一样的值
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
System.out.println(this.name +"开始比较equals");
if (this==obj)
return true;
if (obj!=null&&!(getClass()!=obj.getClass()))
return false;
Student student = (Student) obj;
return this.name.equals(student.name)&&this.age==student.age;
}
public int hashCode(){
System.out.println(this.name +"开始计算HashCode");
return 31*age;
}
}
public class HashCodeAndEqualsTest {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
Student student1 = new Student("菜鸟", 25);
Student student2 = new Student("大佬", 25);
students.add(student1);
students.add(student2);
System.out.println(students);
}
}
运行结果:
菜鸟开始计算HashCode
大佬开始计算HashCode
大佬开始比较equals
[Student{name='菜鸟', age=25}, Student{name='大佬', age=25}]
重写HashCode时将所有的属性计算在内,修改hashCode
public int hashCode(){
System.out.println(this.name +"开始计算HashCode");
return 31*age+this.name.hashCode();
}
运行结果:
菜鸟开始计算HashCode
大佬开始计算HashCode
[Student{name='大佬', age=25}, Student{name='菜鸟', age=25}]
可看到,对于成员变量不同的对象,第二中方式比第一种少了计算equals,提高了计算性能
总结:
- 重写HashCode的时候不应该返回同一个值,而应该将类的成员属性包括在内
- 重写HashCode的时候可将类的成员属性进行线性组合,即:hash = 属性****1的int形式+ C1*属性2的int形式+ C2*属性3的int形式+ …(C1,C2为系数,学过线性代数的更清楚一点~)
- 注意,拼接之后的数值不能超过整形的表达范围。
- 对于引用属性,可直接调用其HashCode即可,就像字符串类型的,直接拿来引用即可
- 采用线性组合可避免不同对象参数hash冲突,提高散列存储结构的利用率