正确使用 hashCode 和 equals 方法_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 正确使用 hashCode 和 equals 方法

正确使用 hashCode 和 equals 方法

 2013/10/11 21:22:30  yangactive  程序员俱乐部  我要评论(0)
  • 摘要:hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。使用hashCode()和equals()hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。重写默认的实现如果你不重写这两个方法,将几乎不遇到任何问题,但是有的时候程序要求我们必须改变一些对象的默认实现
  • 标签:方法 has 使用 Hash 正确

hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。

?

使用hashCode()和equals()

hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。

重写默认的实现

如果你不重写这两个方法,将几乎不遇到任何问题,但是有的时候程序要求我们必须改变一些对象的默认实现。

来看看这个例子,让我们创建一个简单的类Employee

?

monospace !important; float: none !important; height: auto !important; font-size: 10pt !important; vertical-align: baseline !important; font-weight: normal !important; padding-top: 0px; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border: #dddddd 1px solid;">01 class="keyword" style="background-image: none !important; text-align: left !important; padding-bottom: 0px; line-height: 1.1em !important; background-color: #f6f6f6; font-style: normal !important; margin: 0px 2px; padding-left: 5px; width: auto !important; padding-right: 5px; font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace !important; float: none !important; height: auto !important; color: #006699 !important; font-size: 10pt !important; vertical-align: baseline !important; font-weight: bold !important; padding-top: 0px; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border: #dddddd 1px solid;">public?class?Employee 02 { 03 ????private?Integer id; 04 ????private?String firstname; 05 ????private?String lastName; 06 ????private?String department; 07 ? 08 ????public?Integer getId() { 09 ????????return?id; 10 ????} 11 ????public?void?setId(Integer id) { 12 ????????this.id = id; 13 ????} 14 ????public?String getFirstname() { 15 ????????return?firstname; 16 ????} 17 ????public?void?setFirstname(String firstname) { 18 ????????this.firstname = firstname; 19 ????} 20 ????public?String getLastName() { 21 ????????return?lastName; 22 ????} 23 ????public?void?setLastName(String lastName) { 24 ????????this.lastName = lastName; 25 ????} 26 ????public?String getDepartment() { 27 ????????return?department; 28 ????} 29 ????public?void?setDepartment(String department) { 30 ????????this.department = department; 31 ????} 32 }

上面的Employee类只是有一些非常基础的属性和getter、setter.现在来考虑一个你需要比较两个employee的情形。

?

?

01 public?class?EqualsTest { 02 ????public?static?void?main(String[] args) { 03 ????????Employee e1 =?new?Employee(); 04 ????????Employee e2 =?new?Employee(); 05 ? 06 ????????e1.setId(100); 07 ????????e2.setId(100); 08 ????????//Prints false in console 09 ????????System.out.println(e1.equals(e2)); 10 ????} 11 }

毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个employee。真正的商业逻辑希望我们返回true。?
为了达到这个目的,我们需要重写equals方法。?

01 public?boolean?equals(Object o) { 02 ????????if(o ==?null) 03 ????????{ 04 ????????????return?false; 05 ????????} 06 ????????if?(o ==?this) 07 ????????{ 08 ???????????return?true; 09 ????????} 10 ????????if?(getClass() != o.getClass()) 11 ????????{ 12 ????????????return?false; 13 ????????} 14 ????????Employee e = (Employee) o; 15 ????????return?(this.getId() == e.getId()); 16 }

在上面的类中添加这个方法,EauqlsTest将会输出true。?
So are we done?没有,让我们换一种测试方法来看看。?

01 import?java.util.HashSet; 02 import?java.util.Set; 03 ? 04 public?class?EqualsTest 05 { 06 ????public?static?void?main(String[] args) 07 ????{ 08 ????????Employee e1 =?new?Employee(); 09 ????????Employee e2 =?new?Employee(); 10 ? 11 ????????e1.setId(100); 12 ????????e2.setId(100); 13 ? 14 ????????//Prints 'true' 15 ????????System.out.println(e1.equals(e2)); 16 ? 17 ????????Set<Employee> employees =?new?HashSet<Employee>(); 18 ????????employees.add(e1); 19 ????????employees.add(e2); 20 ????????//Prints two objects 21 ????????System.out.println(employees); 22 ????}

上面的程序输出的结果是两个。如果两个employee对象equals返回true,Set中应该只存储一个对象才对,问题在哪里呢??
我们忘掉了第二个重要的方法hashCode()。就像JDK的Javadoc中所说的一样,如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。

1 @Override 2 ?public?int?hashCode() 3 ?{ 4 ????final?int?PRIME =?31; 5 ????int?result =?1; 6 ????result = PRIME * result + getId(); 7 ????return?result; 8 ?}

使用Apache Commons Lang包重写hashCode() 和equals()方法?
Apache Commons 包提供了两个非常优秀的类来生成hashCode()和equals()方法。看下面的程序。

?

view source print? 01 import?org.apache.commons.lang3.builder.EqualsBuilder; 02 import?org.apache.commons.lang3.builder.HashCodeBuilder; 03 public?class?Employee 04 { 05 ?private?Integer id; 06 ?private?String firstname; 07 ?private?String lastName; 08 ?private?String department; 09 public?Integer getId() { 10 ????return?id; 11 ?} 12 ?public?void?setId(Integer id) { 13 ????this.id = id; 14 ?} 15 ?public?String getFirstname() { 16 ????return?firstname; 17 ?} 18 ?public?void?setFirstname(String firstname) { 19 ????this.firstname = firstname; 20 ?} 21 ?public?String getLastName() { 22 ????return?lastName; 23 ?} 24 ?public?void?setLastName(String lastName) { 25 ????this.lastName = lastName; 26 ?} 27 ?public?String getDepartment() { 28 ????return?department; 29 ?} 30 ?public?void?setDepartment(String department) { 31 ????this.department = department; 32 ?} 33 @Override 34 ?public?int?hashCode() 35 ?{ 36 ????final?int?PRIME =?31; 37 ????return?new?HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME). 38 ???????????toHashCode(); 39 ?} 40 @Override 41 ?public?boolean?equals(Object o) { 42 ????if?(o ==?null) 43 ???????return?false; 44 ????if?(o ==?this) 45 ???????return?true; 46 ????if?(o.getClass() != getClass()) 47 ???????return?false; 48 ????Employee e = (Employee) o; 49 ???????return?new?EqualsBuilder(). 50 ??????????????append(getId(), e.getId()). 51 ??????????????isEquals(); 52 ????} 53 ?}

如果你使用Eclipse或者其他的IDE,IDE也可能会提供生成良好的hashCode()方法和equals()方法。?

需要注意记住的事情

  • 尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在我们的案例中,我们使用员工id。
  • eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值)
  • 任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。
  • 两者必须同时重写。

当使用ORM的时候特别要注意的

  • 如果你使用ORM处理一些对象的话,你要确保在hashCode()和equals()对象中使用getter和setter而不是直接引用成员变量。因为在ORM中有的时候成员变量会被延时加载,这些变量只有当getter方法被调用的时候才真正可用。
  • 例如在我们的例子中,如果我们使用e1.id == e2.id则可能会出现这个问题,但是我们使用e1.getId() == e2.getId()就不会出现这个问题。
  • public int hashCode() {?

    ? ??int h = hash;?
    ? int len = count;?
    ? if (h == 0 && len > 0) {?
    ? int off = offset;?
    ? char val[] = value;?
    ? for (int i = 0; i < len; i++) {?
    ? ? ?h = 31*h + val[off++];?
    ? ?}?
    ? hash = h;?
    ? }?
    ? return h;?
    }?

    该函数是我看的函数接口源码,为什么要使用31这个数呢?

    ?

    其实上面的实现也可以总结成数数里面下面这样的公式:

    s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

  • A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

    B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)

    C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)

    D.并且31只占用5bits,相乘造成数据溢出的概率较小。

发表评论
用户名: 匿名