首先,equals()方法和hashCode()方法都来自于Object类的定义,Java类都继承了这两个方法,都定义了本身的实现。java
equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么?算法
在object类中,此标尺即为==。ide
public boolean equals(Object obj) { return (this == obj); }
固然,这个标尺不是固定的,其余类中能够按照实际的须要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。性能
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
如此能够增长类的功能性和实际编码的灵活性。固然了,若是自定义的类没有重写equals()方法来从新定义此标尺,那么默认的将是其父类的equals(),直到Object基类。ui
以下场景的实际业务需求,对于User bean,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。则能够重写equals以重定义User对象相等的标尺。this
package com.corn.objectsummary; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } protected String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof User)) { return false; } if (((User) obj).getUid() == this.getUid()) { return true; } return false; } }
package com.corn.objectsummary; public class ObjectTest implements Cloneable { public static void main(String[] args) { User u1 = new User(); u1.setUid(111); u1.setName("张三"); User u2 = new User(); u2.setUid(111); u2.setName("张三丰"); System.out.println(u1.equals(u2)); //返回true } }
ObjectTest中打印出true,由于User类定义中重写了equals()方法,这很好理解,极可能张三是一我的小名,张三丰才是其大名,判断这两我的是否是同一我的,这时只用判断uid是否相同便可。编码
重写equals()方法遵循的准则:spa
对称性:若是x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。code
反射性:x.equals(x)必须返回是“true”。对象
类推性:若是x.equals(y)返回是“true”,并且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
还有一致性:若是x.equals(y)返回是“true”,只要x和y内容一直不变,无论你重复x.equals(y)多少次,返回都是“true”。
任何状况下,x.equals(null),永远返回是“false”;x.equals(和x不一样类型的对象)永远返回是“false”。
如上重写equals方法表面上看上去是能够了,实则否则。由于它破坏了Java中的约定:重写equals()方法必须重写hasCode()方法。
hashCode():方法返回一个整形数值,表示该对象的哈希码值。
hashCode()具备以下约定:
1).在Java应用程序程序执行期间,对于同一对象屡次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未作修改。在Java应用程序的一次执行到另一次执行,同一对象的hashCode()返回的哈希码无须保持一致;
2).若是两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不必定相等。
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。所以,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时能够推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。
可能有人在此产生疑问:既然比较两个对象是否相等的惟一条件(也是次要条件)是equals,那么为何还要弄出一个hashCode(),而且进行如此约定,弄得这么麻烦?
其实,这主要体如今hashCode()方法的做用上,其主要用于加强哈希表的性能。
以集合类中,以Set为例,当新加一个对象时,须要判断现有集合中是否已经存在与此对象相等的对象,若是没有hashCode()方法,须要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。经过借助于hasCode方法,先计算出即将新加入对象的哈希码,而后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象便可。(注:Set的底层用的是Map的原理实现)
在此须要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不必定是对象的逻辑地址,hashCode()相同的两个对象,不必定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。
所以,在上述代码中,重写了equals()方法后,须要重写hashCode()方法。
package com.corn.objectsummary; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } protected String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof User)) { return false; } if (((User) obj).getUid() == this.getUid()) { return true; } return false; } @Override public int hashCode() { int result = 17; result = 31 * result + this.getUid(); return result; } }
注:上述hashCode()的重写中出现了result*31,是由于result*31 = (result<<5) - result。之因此选择31,是由于左移运算和减运算计算效率远大于乘法运算。固然,也能够选择其余数字。