Java equals 与 hashCode 相关题 (11)
https://mp.weixin.qq.com/s?__biz=MzI3ODc3NzQ4NQ==&mid=2247483781&idx=1&sn=e28f75567517fcedeb2f449a72c8c277&chksm=eb509a67dc2713718d032c2452ba17e0c34f69999b266c520d1fbab8fd0698a3050323253a47&scene=21#wechat_redirect
问:两个对象值相同 (tmp1.equals(tmp2) == true) 但却可有不同的 HashCode 值,这句话有问题吗?
答:有问题,这句话是不对的。两个对象 tmp1 和 tmp2 满足 tmp1.equals(tmp2) == true 时它们的 HashCode 应当相同,因为 Java 对于 eqauls 方法和 hashCode 方法的规定是如果两个对象 equals 方法相等则它们的 hashCode 值一定要相同,如果两个对象的 hashCode 相同则它们的 equals 方法并不一定相同;实际中我们也可以不按照要求的原则去做,但是如果违背了上述原则就会发现在使用容器时相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
问:可以直接根据 hashCode() 方法产生的值判断两个对象是否相等吗?
答:不能。因为 hashCode()方法的由来是根据这个对象内存储的数据及对象的一些特征来做散列并返回一个有符号的 32 位哈希值,所以 hashCode() 方法返回的是一个散列值,而对于一个散列来说不同的内容也是可能会出现相同的散列值,所以即使两个对象的 hashCode() 返回值一样也并不能代表两个对象是相等的,要判断两个对象是否相等还是需要使用 equals() 方法。不过要注意:两个对象的 hashCode() 返回值相等不能判断这两个对象是相等的,但是两个对象的 hashCode() 返回值不相等则可以判断这两个对象一定不相等(原因如上题指导原则)。
问:说说 hashCode() 的返回值和 == 的关系?
答:若 == 返回 true 则两边对象的 hashCode() 返回值必须相等,若 == 返回 false 则两边对象的 hashCode() 返回值有可能相等,也有可能不等;因为在 Java 中对象默认的 equals 方法实现就是 == 比较,而 Java 对于 eqauls 方法和 hashCode 方法的规定是如果两个对象 equals 方法相等则它们的 hashCode 值一定要相同,如果两个对象的 hashCode 相同则它们的 equals 方法并不一定相同,所以可得出上面结论。
package javabasics.equilsandhashcode; /** * 针对equils与hashcode来定义 * * * */ public class EquilsDemo { public EquilsDemo(String obj){} public static void main(String[] args) { String obj1 = new String("obj1"); String obj2 = obj1; System.out.println("equils:" + obj1.equals(obj2)); System.out.println("equils hashcode:" + (obj1==obj2)); System.out.println("obj1 hashcode:" + obj1.hashCode() + " obj2 hashcode:" + obj2.hashCode()); obj2 = new String("obj1"); System.out.println("equils:" + obj1.equals(obj2)); System.out.println("equils hashcode:" + (obj1==obj2)); System.out.println("obj1 hashcode:" + obj1.hashCode() + " obj2 hashcode:" + obj2.hashCode()); System.out.println("******************"); EquilsDemo wquilsDemo = new EquilsDemo("EquilsDemo1"); EquilsDemo wquilsDemo2 = wquilsDemo; System.out.println("wquilsDemo:" + wquilsDemo.equals(wquilsDemo2)); System.out.println("equils hashcode:" + (wquilsDemo==wquilsDemo2)); System.out.println("wquilsDemo hashcode:" + wquilsDemo.hashCode() + " wquilsDemo2 hashcode:" + wquilsDemo2.hashCode()); System.out.println("******************"); wquilsDemo2 = new EquilsDemo("EquilsDemo1"); System.out.println("equils:" + wquilsDemo.equals(wquilsDemo2)); System.out.println("equils hashcode:" + (wquilsDemo==wquilsDemo2)); System.out.println("wquilsDemo hashcode:" + wquilsDemo.hashCode() + " wquilsDemo2 hashcode:" + wquilsDemo2.hashCode()); } }
输出结果:
E:\officesoftware\Java\jdk1.8.0_192\bin\java.exe "-javaagent:E:\officesoftware\ides\IntelliJ IDEA 2018.3\lib\idea_rt.jar=52056:E:\officesoftware\ides\IntelliJ IDEA 2018.3\bin" -Dfile.encoding=UTF-8 -classpath E:\officesoftware\Java\jdk1.8.0_192\jre\lib\charsets.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\deploy.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\access-bridge-64.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\cldrdata.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\dnsns.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\jaccess.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\jfxrt.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\localedata.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\nashorn.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\sunec.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\sunjce_provider.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\sunmscapi.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\sunpkcs11.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\ext\zipfs.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\javaws.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\jce.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\jfr.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\jfxswt.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\jsse.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\management-agent.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\plugin.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\resources.jar;E:\officesoftware\Java\jdk1.8.0_192\jre\lib\rt.jar;E:\officesoftware\apache-tomcat-9.0.13\webapps\production\firstweb1;E:\officesoftware\apache-tomcat-9.0.13\lib\servlet-api.jar;C:\Users\Nxin\.m2\repository\org\jasig\cas\client\cas-client-core\3.4.1\cas-client-core-3.4.1.jar javabasics.equilsandhashcode.EquilsDemo equils:true equils hashcode:true obj1 hashcode:3404314 obj2 hashcode:3404314 equils:true equils hashcode:false obj1 hashcode:3404314 obj2 hashcode:3404314 ****************** wquilsDemo:true equils hashcode:true wquilsDemo hashcode:1735600054 wquilsDemo2 hashcode:1735600054 ****************** equils:false equils hashcode:false wquilsDemo hashcode:1735600054 wquilsDemo2 hashcode:21685669 Process finished with exit code 0
问:Java 中 hashCode() 的作用是什么?
答:hashCode() 的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;只有每个对象的 hash 码尽可能不同才能保证散列的存取性能,事实上 Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码)。在 Java 有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的 equals 方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,也就是说如果集合中现在已经有 3000 个元素则第 3001 个元素加入集合时就要调用 3000 次 equals 方法,这显然会大大降低效率,于是 Java 采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的 hashCode 方法就一下子能定位到它应该放置的物理位置上(实际可能并不是),如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址,这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次,而 hashCode 的值对于每个对象实例来说是一个固定值。
问:Java 中为什么重写 equals(str) 方法时尽量要重写 hashCode() 方法?
答:这是一个检测实际项目踩坑经验的题目,因为我们经常会犯的一个很常见而又低级的错误根源在于重写 equals 方法时没有重写 hashCode 方法。自定义类重写 equals 方法是用来进行等值比较,重写 compareTo 方法是用来进行不同对象大小比较,而重写 hashCode 方法是为了将数据存入 HashSet、HashMap、Hashtable 等基于哈西表的集合类时进行高效比较。
当 equals 方法被重写时通常有必要重写 hashCode 方法来维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码,如果不这样做的话就会违反 hashCode 方法的常规约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet、Hashtable 等。
hashCode 方法的常规约定如下:
-
程序执行期间只要对象 equals 方法比较操作所用到的信息没有被修改,则对这同一个对象无论调用多次 hashCode 方法都必须返回同一个整数。
-
如果两个对象根据 equals 方法比较是相等的则调用这两个对象中任意一个对象的 hashCode 方法都必须产生同样的整数结果。
-
如果两个对象根据 equals 方法比较是不相等的,则调用这两个对象中任意一个对象的 hashCode 方法不一定要产生相同的整数结果(尽量保证不相等的对象产生截然不同的整数结果是可以提高散列表性能的)。
问:请补全下面代码空缺(即实现其 hashCode 方法)?
-
public class UserBean { public String name; public int age; public String pwd; @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof UserBean)) { return false; } UserBean user = (UserBean) o; return user.name.equals(name) && user.age == age && user.pwd.equals(pwd); } @Override public int hashCode() { //请在此处正确的实现该方法 } }
答:此题就是在考察如何在重写 equals() 方法时正确的重写 hashCode() 方法。其实重写 hashcode() 方法有如下几个原则可以遵循:
-
如果重写了 equals() 方法,且 equals() 方法判断相等则 hashCode() 方法也要保证必须相等。
-
重写 hashCode() 方法算法也不能太过简单,否则哈希冲突过多。
-
重写 hashCode() 方法算法也不能太过复杂,否则计算复杂度过高而影响性能。
《Effective Java》书中给出的一种算法,基于 17 和 31 散列码思想的实现,如下:
-
public int hashCode() { int result = 17; result = 31 * result + name.hashCode(); result = 31 * result + age; result = 31 * result + pwd.hashCode(); return result; }
当然,这道题如果不限制 equals 方法的实现则完全可以用 JDK7 开始提供的 java.util.Objects 来重写 equals 和 hashCode 方法,代码如下:
-
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof UserBean)) { return false; } User user = (User) o; return age == user.age && Objects.equals(name, user.name) && Objects.equals(pwd, user.pwd); } public int hashCode() { return Objects.hash(name, age, pwd); }
使用上面这种 Objects 方式重写 equals 和 hashCode 可能是最简单和最优雅的实现了,所以我们项目中推荐采纳。