`
wsmajunfeng
  • 浏览: 498208 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

解析java中对equals和hashCode方法的约定

 
阅读更多

JDK的java.lang.Object类中实现了equals函数,其定义说明如下:

写道
public boolean equals(Object obj)
Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
[list=5]

It is reflexive: for any non-null reference value x, x.equals(x) should return true.

It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

For any non-null reference value x, x.equals(null) should return false.
[/list]
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

 其具体的实现代码如下所示:

写道
public boolean equals(Object obj) {
return (this == obj);
}

 从上面的代码中可以看出,Object类的equals实现只是简单地调用了“==”运算符,也即定义中说明的,对于两个非空对象X和Y,有且仅当X和Y 指向同一个对象时该函数才返回true。由于Object类是java中所有类的超类,因而其它任何类都默认继承了此函数。在大多数情况下,此默认的实现 方式是合适的,因为任一个类本质上都是唯一的;但是对于那些表示“值”的类(如Integer、String等),它们引入了自己特有的“逻辑相等”的概 念,此时就必须修改equals函数的实现。
例如java.lang.String类的实现如下:

 public boolean equals(Object anObject) {  
       if (this == anObject) {  
    return true;  
}  
if (anObject instanceof String) {  
    String anotherString = (String)anObject;  
    int n = count;  
    if (n == anotherString.count) {  
    char v1[] = value;  
    char v2[] = anotherString.value;  
    int i = offset;  
    int j = anotherString.offset;  
    while (n-- != 0) {  
        if (v1[i++] != v2[j++])  
        return false;  
    }  
    return true;  
    }  
}  
return false;  
   }  

而java.lang.Integer类的实现如下: 

Java代码   收藏代码
  1. public boolean equals(Object obj) {  
  2.     if (obj instanceof Integer) {  
  3.         return value == ((Integer)obj).intValue();  
  4.     }  
  5.     return false;  
  6.     }  


      String类的equals实现使得当两个String对象所表示的字符串的值是一样的时候就能返回true;而Integer类则当两个Integer对象所持有的整数值是一样的时候就能返回true。 
      因此,当我们我们自己编写的类需要引入“逻辑相等”的概念,且其超类又没有改写equals的实现时,我们就必须实现自己的equals函数。在上面Object类中关于equals函数的说明中,定义了5条改写equals函数所必须遵守的规范。其中第1条自反性(reflexivity)和第5条通常能够自行满足。下面我们重点介绍下第2点对称性(symmetric)、第3点传递性(transitive)和第4点一致性(consistent)。 
      对称性 
即对于任意两个引用值X和Y,X.equals(Y) 和 Y.equals(X) 返回的结果必须是一致的。下面引用《effect java》上的例子来说明了违反这一特性的后果:

Java代码   收藏代码
  1. public class CaseInsensitiveString {  
  2.       
  3.     private String s;  
  4.       
  5.     public boolean equals(Object obj){  
  6.         if(obj instanceof CaseInsensitiveString)  
  7.             return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s);  
  8.         if(obj instanceof String)  
  9.             return s.equalsIgnoreCase((String)obj);  
  10.         return false;  
  11.     }  
  12. }  


然后我们定义如下的类来进行测试:

Java代码   收藏代码
  1. public void test(){  
  2.     CaseInsensitiveString cis = new CaseInsensitiveString("Value");  
  3.     String s = "Value";  
  4.     List list = new ArrayList();  
  5.     list.add(cis);  
  6.     System.out.println(cis.equals(s));  
  7.     System.out.println(s.equals(cis));  
  8.     System.out.println(list.contains(s));  
  9. }  

 


运行test(),第一条语句的输出为true,而后两条为false。因为CaseInsensitiveString类中equals函数对String对象的比较也可能返回true,只要其大小写不敏感的字符串值相等;而在String类中对任何非String对象都返回false,这样CaseInsensitiveString的实现就违反了对称性,从而导致了第三条语句也返回了意想不到的结果false。这是因为ArrayList的contains函数实现是用实参s对list中每一个对象调用equals函数,若有equals函数返回true,则contains返回true;因而在这里contains函数必然返回false。因此,如果违反了对称性,则可能会得到意料之外的结果。解决此问题的一个办法就是在CaseInsensitiveString类的equals函数中对Stirng对象的比较无论值是否一致都返回false。

传递性 
即对于任意的引用值X、Y和Z,如果 X.equals(Y) 和 Y.equals(Z) 都返回true,则 X.equals(Z) 也一定返回true。下面举例说明: 

Java代码   收藏代码
  1.  public class Point {  
  2.     private final int x;  
  3.   
  4.     private final int y;  
  5.   
  6.     public Point(int x, int y) {  
  7.         this.x = x;  
  8.         this.y = y;  
  9.     }  
  10.   
  11.     public boolean equals(Object obj) {  
  12.         if (!(obj instanceof Point))  
  13.             return false;  
  14.         Point p = (Point) obj;  
  15.         return p.x == x && p.y == y;  
  16.     }  
  17. }  
  18.   
  19. public class ColorPoint extends Point {  
  20.   
  21.     private int z;  
  22.   
  23.     public ColorPoint(int x, int y, int z) {  
  24.         super(x, y);  
  25.         this.z = z;  
  26.     }  
  27.   
  28.     public boolean equals(Object obj) {  
  29.         if (!(obj instanceof Point))  
  30.             return false;  
  31.         if(obj instanceof ColorPoint){
  32. ColorPoint cp = (ColorPoint) obj;  
  33.   return super.equals(obj) && cp.z == this.z; 
  34. }else{
  35. Point cp = (Point) obj; 
  36. return super.equals(obj)
  37. }
  38.         
  39.     }  
  40. }  


在上面的代码中定义了类Point及其子类ColorPoint,并分别改写了equals函数。下面运行如下代码来测试:

Java代码   收藏代码
  1. ColorPoint cp1 = new ColorPoint(1,2,3);  
  2. ColorPoint cp2 = new ColorPoint(1,2,4);  
  3. Point p = new Point(1,2);  
  4. System.out.println(cp1.equals(p));  
  5. System.out.println(p.equals(cp2));  
  6. System.out.println(cp1.equals(cp2));  


结果前两条语句输出“true”,而最后一条语句输出为“false”;从而违反了传递性规范。其主要原因是在基类的equals函数中并未对子类加以区分,从而使得基类与子类之间的比较也能返回true。要解决此方法,《effect java》上提供了“复合代替继承”的方法;另外,如果可以将基类和子类视为不等的话,使用 getClass()函数代替 instanceof 运算符进行比较可以很好的解决这一问题。getClass()会返回此对象的运行期类(runtime class),因为基类和子类属于不同的class,getClass()能够将其区分开来。下面是一段示例的代码:

Java代码   收藏代码
  1. public boolean equals(Object obj) {  
  2.         if(obj.getClass() != this.getClass())  
  3.             return false;  
  4.         Point p = (Point) obj;  
  5.         return p.x == x && p.y == y;  
  6.           
  7.     }  


      一致性 
即如果两个对象相等的话,那么它们必须始终保持相等,除非至少有一个对象被修改了。 

在Object类equals函数的说明中的最后一段提到当我们改写equals函数的时候,通常都需要改写hashCode函数,后者同样在Object类中进行了定义,如下:

引用

public int hashCode() 
Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable. 
The general contract of hashCode is: 
[list=3]
  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
  • [/list] 
    As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.) 


    hashCode()函数返回一个对象的散列值(hash code),在java中有些集合类都是基于散列值的,如HashMap、HashSet、Hashtable等;它们都根据对象的散列值将其映射到相应的散列桶中。如Hashtable的put和get函数的实现如下所示:

    Java代码   收藏代码
    1.   public synchronized V get(Object key) {  
    2. Entry tab[] = table;  
    3. int hash = key.hashCode();  
    4. int index = (hash & 0x7FFFFFFF) % tab.length;  
    5. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
    6.     if ((e.hash == hash) && e.key.equals(key)) {  
    7.     return e.value;  
    8.     }  
    9. }  
    10. return null;  
    11.    }  
    12.   
    13. ublic synchronized V put(K key, V value) {  
    14. // Make sure the value is not null  
    15. if (value == null) {  
    16.     throw new NullPointerException();  
    17. }  
    18.   
    19. // Makes sure the key is not already in the hashtable.  
    20. Entry tab[] = table;  
    21. int hash = key.hashCode();  
    22. int index = (hash & 0x7FFFFFFF) % tab.length;  
    23. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
    24.     if ((e.hash == hash) && e.key.equals(key)) {  
    25.     V old = e.value;  
    26.     e.value = value;  
    27.     return old;  
    28.     }  
    29. }  
    30.   
    31. modCount++;  
    32. if (count >= threshold) {  
    33.     // Rehash the table if the threshold is exceeded  
    34.     rehash();  
    35.   
    36.            tab = table;  
    37.            index = (hash & 0x7FFFFFFF) % tab.length;  
    38. }   
    39.   
    40. // Creates the new entry.  
    41. Entry<K,V> e = tab[index];  
    42. tab[index] = new Entry<K,V>(hash, key, value, e);  
    43. count++;  
    44. return null;  
    45.    }  


    因而hashCode函数极大地影响了这些集合类的正常工作。如果在改写完equals函数后,不相应改写hashCode函数,则可能会得不到想要的结果。如下例所示:

    Java代码   收藏代码
    1. Point p1 = new Point(12);  
    2. Point p2 = new Point(12);  
    3. Hashtable<Point, String> ht = new Hashtable<Point, String>();  
    4. ht.put(p1, "value");  
    5. System.out.println(p1.equals(p2));  
    6. System.out.println(ht.get(p2));  

     


    由上面Point类的实现,p1和p2是相等的,第一个语句正常输出true;但是第二个语句输出的值是null,并没有得到期望中的“value”。导致这一问题的根本原因是Point类改写了equals函数,对相等的概念作了更改,但没有相应更改hashCode函数,使得两个相等的函数拥有不同的hashCode,违反了Object类关于hashCode的约定中的第2点,从而返回了错误的结果。 
    在Object类中定义的几个hashCode约定如下: 
    1. 在同一应用中,一个对象的hashCode函数在equals函数没有更改的情况下,无论调用多少次,它都必须返回同一个整数。 
    2. 两个对象如果调用equals函数是相等的话,那么调用hashCode函数一定会返回相同的整数。 
    3. 两个对象如果调用equals函数是不相等的话,那么调用hashCode函数不要求一定返回不同的整数。 
    我们在改写equals 和 hashCode 函数的时候,一定要遵守如上3条约定,在改写equals的同时也改写hashCode的实现,这样才能保证得到正确的结果。

     

     

     

     

    分享到:
    评论

    相关推荐

      Java equals 方法与hashcode 方法的深入解析.rar

      在Java编程语言中,`equals()`方法和`hashCode()`方法是两个非常重要的概念,它们主要用于对象的比较和哈希表的高效运作。本解析将深入探讨这两个方法的用途、实现原理以及它们之间的关联。 首先,`equals()`方法是...

      JAVA之Object类所有方法

      `equals()`和`hashCode()`应满足一致性的约定:如果两个对象相等(根据`equals()`),那么它们的哈希码必须相同。 3. **toString()**: 返回一个表示该对象的字符串,通常是类名加上对象的属性值。在调试和打印输出...

      java面试题(1).pdf

      在Java中,所有的对象都继承自Object类,而Object类中定义了hashCode()方法和equals()方法。根据Java的约定,如果两个对象通过equals方法比较是相等的,那么这两个对象的hashCode()方法返回的散列码(整数)也应当...

      黑马集合知识测试(带解析).doc

      9. 重写 equals 和 hashCode 方法时,需要遵守 equals 方法的约定,hashCode 值相同的两个对象的 equals 方法返回值不一定为 true。 10. 将 Map 集合中的键存储到 Set 集合可以使用 keySet() 方法,entrySet() 方法...

      常用JAVA面试题库4

      这是基于Object类的equals()和hashCode()方法约定的,如果不遵循这个约定,可能会导致哈希表(如HashMap)的行为异常。 7. **值传递与引用传递**: - 在Java中,所有参数传递都是值传递。当对象作为参数传递时,...

      JAVA面试题和答案.docx

      如果希望比较对象的内容,必须在类中实现`equals()`方法,通常同时还需要重写`hashCode()`方法以保持一致性的约定。 例如,假设有一个`Cat`类,没有重写`equals()`方法: ```java class Cat { private String ...

      java面试题及其答案

      根据Java的约定,如果两个对象通过`equals()`方法判断为相等,那么它们的`hashCode()`必须相同。但是,两个不相等的对象可能具有相同的`hashCode()`,这取决于哈希函数的设计。因此,即使`hashCode()`相同,仍需通过...

      Java+最常见200+面试题全解析.doc

      根据约定,如果两个对象相等(即 equals() 返回 true),那么它们的 hashCode() 必须相同,但反之不成立。 6. **String 是否为基本数据类型**: 不是,String 是一个对象,属于引用数据类型,它在 Java 中的地位...

      java-专业】最全-JAVA面试题

      6. **hash code一致性**:两个对象值相同(x.equals(y) == true),其hash code也应相同,这是hashCode方法的约定。 7. **对象参数传递**:Java总是进行值传递,但对象引用作为参数时,方法内部可以改变对象的属性,...

      java原始数据类型的包装类

      - `hashCode()`和`equals()`:定义了对象的哈希码和相等性检查,符合Java对象的一般约定。 - `parseInt(String s)`:解析字符串为指定类型的数值,如果解析失败则抛出NumberFormatException。 10. **自动类型转换...

      java资源题库

      这是因为`equals`和`hashCode`方法之间有约定:如果两个对象相等(通过`equals`判断),那么它们的哈希码应该相同。这在使用哈希表结构的数据结构(如`HashSet`)时尤为重要。 #### 九、HashMap的工作原理 1. **...

      全面解析java中的hashtable

      哈希函数是通过键的`hashCode()`方法生成的,它返回一个整数值,然后除以桶的数量取余数,决定键值对在哈希表中的位置。如果多个键的哈希值相同,它们会被放在同一个桶内,此时需要使用`equals()`方法来区分不同的键...

      java面试题(20210925010116)[参照].pdf

      - 根据Java的约定,如果两个对象通过 `equals()` 方法比较结果为 true,那么它们的 `hashCode()` 方法应该返回相同的值。然而,两个具有相同 `hashCode()` 的对象并不意味着它们通过 `equals()` 方法比较会返回 ...

      java面试题大全好多经典的试题

      根据`Object`类中`equals()`方法和`hashCode()`方法的约定,如果两个对象通过`equals()`方法比较相等,则它们必须具有相同的哈希码。 **19. String对象是否可变?** 不是,`String`对象是不可变的,即一旦创建之后...

      core java达内培训资料

      根据给定的文件信息,以下是对“core java达内培训资料”中涉及的关键知识点的深入解析: ### 面向对象(OO) 面向对象编程(OOP)是Java的核心概念之一,它强调通过“对象”来设计和实现软件系统。在Java中,一切皆为...

      BAT——Java面试宝典Beta5.0

      关于`equals()`和`hashCode()`,Java中的约定是,如果两个对象相等(`equals()`返回`true`),它们的`hashCode()`应当相同,但反之不成立,这是为了解决哈希表的效率问题。最后,`String`类在Java中被设计为不可继承...

      中兴通讯Java面试题.doc

      - **代码组织**:遵循经典的类设计模式,实现`equals()`、`hashCode()`和`toString()`等基本方法。 - **代码注释**:保持良好的注释习惯,确保代码可读性和可维护性。 - **编码规范**:遵循项目或团队的编码标准,...

      Java 最常见的 208 道面试题:第一模块答案

      【Java基础】 ...以上就是Java面试中常见的一些问题及其解析,涵盖了Java基础、字符串操作、面向对象特性以及I/O流等方面的知识点。理解并掌握这些内容对于提升Java编程技能和应对面试非常有帮助。

      2019最新Java面试题,常见面试题及答案汇总说课材料.pdf

      以下是对2019年最新Java面试题的详细解答和解析: 1. **JDK和JRE的区别**:JDK(Java Development Kit)是Java开发工具包,包含了编译器、调试器等开发工具以及JRE;JRE(Java Runtime Environment)则是Java运行...

      2023java后端招聘用面试题.docx

      以下是对这些知识点的详细解析: 1. **字符串比较**: - `equals()` 方法用于比较字符串内容是否一致,而`==`则比较的是对象的引用,即内存地址。因此,对于`String`对象,应当使用`equals()`或`Objects.equals()`...

    Global site tag (gtag.js) - Google Analytics