Java hashCode()方法指南 | 您所在的位置:网站首页 › java哈希算法 › Java hashCode()方法指南 |
Java hashCode()方法指南
哈希是计算机学科基本概念之一。 在java中,一些常用集合都基于高效哈希算法。如HashMap、HashSet。 本文我们将重点讨论hashCode()工作原理以及如何在集合中发挥作用。 数据结构中使用hashCode()方法一定场景下,常用集合搜索操作是非常低效的。举例,包含大量元素的list中触发线性搜索: List words = Arrays.asList("Welcome", "to", "Baeldung"); if (words.contains("Baeldung")) { System.out.println("Baeldung is in the list"); }java中提供一些数据结构可以解决这类问题,如几个实现Map接口的哈希实现类。 使用哈希算法的集合通过hashCode()方法计算给定键的哈希值,然后内部使用该值去存储数据,为了查询操作更有效率。 理解hashCode() 方法工作原理简单地说,hashCode() 方法通过哈希算法生成一个整数值。 相对的对象(通过equals方法判断)返回相同哈希值,不同对象返回不同哈希值不是必须的。 hashCode()方法的算法约定为: 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 两个相对的对象(通过equals方法判断)必须返回相同哈希值。 两个不相对的对象(通过equals方法判断),调用hashCode()方法返回值不是必须不相等。但开发者需了解,不同对象返回不同的哈希值会提升效率。 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 本地hashCode() 方法实现实际上有一个完全遵循上述契约的hashCode()方法实现非常简单。 为了演示,我们定义一个示例User类,它覆盖了该方法的默认实现: public class User { private long id; private String name; private String email; // standard getters/setters/constructors @Override public int hashCode() { return 1; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (this.getClass() != o.getClass()) return false; User user = (User) o; return id != user.id && (!name.equals(user.name) && !email.equals(user.email)); } // getters and setters here }User类提供自定义equal和hashCode方法,完全遵循各自的约定,即使hashCode方法返回任何固定值也不是非法的。 然而,这个实现完全降低哈希表的功能,每个对象将保存在相同位置,单一桶中。这时哈希表查询将执行线性搜索,完全没有带来任何优势。 改善hashCode()方法的实现让我们稍微改善上述hashCode()方法的实现,使用User类的所有字段,为了能够让不相等的对象生成不同的结果。 @Override public int hashCode() { return (int) id * name.hashCode() * email.hashCode(); }这个基本的哈希算法肯定要比前一个好得多,因为它只是通过将名称和电子邮件字段和id的哈希代码相乘来计算对象的哈希码。 一般来说,我们可以说这是一个合理的hashCode()实现,只要我们使equals()实现与它保持一致。 标准hashCode()实现计算哈希值的哈希算法越好,哈希表的性能就越好。下面我们讨论标准实现,使用两个素数来增加计算哈希值的唯一性。 @Override public int hashCode() { int hash = 7; hash = 31 * hash + (int) id; hash = 31 * hash + (name == null ? 0 : name.hashCode()); hash = 31 * hash + (email == null ? 0 : email.hashCode()); return hash; }尽管理解hashCode()和equals()方法所扮演的角色非常重要,但我们无需每次都从头实现,大多数ide都提供了生成自定义的hashCode()和equals()实现。从java 7 之后,Objects.hash() 工具方法可以方便实现: Objects.hash(name, email)IntelliJ IDEA 生成下面实现: @Override public int hashCode() { int result = (int) (id ^ (id >>> 32)); result = 31 * result + name.hashCode(); result = 31 * result + email.hashCode(); return result; }Eclipse生成实现如下: @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + (int) (id ^ (id >>> 32)); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; }除了上述基于IDE的hashCode实现,也可以通过其他工具进行有效实现,如Lombok,其maven依赖如下: org.projectlombok lombok-maven 1.16.18.0 pom现在User类仅需要增加@EqualsAndHashCode注解就可以实现: @EqualsAndHashCode public class User { // fields and methods here }类似使用 Apache Commons Lang’s HashCodeBuilder 类也可以生成 hashCode() 实现, commons-lang Maven 依赖如下: commons-lang commons-lang 2.6hashCode()方法实现如下: public class User { public int hashCode() { return new HashCodeBuilder(17, 37). append(id). append(name). append(email). toHashCode(); } }一般来说,在实现hashCode()时没有通用的诀窍。强烈建议您阅读 Joshua Bloch’s Effective Java, ,它提供了实现高效散列算法的完整指南。 这里我们注意到所有这些实现都以某种形式使用数字31——这是因为31有一个很好的属性——它的乘法运算可以被比标准乘法运算快的位移所代替: 31 * i == (i |
CopyRight 2018-2019 实验室设备网 版权所有 |