• hashcode和equals方法详细解析, hashmap对于hashcode方法的使用


    一 第一篇 http://jameswxx.iteye.com/blog/647451  字符串引出来

             前几天有个同事问我,String a="123",String b=new String("123");它们的hashcode相等吗?我当时愣了一下,首先它们的equals肯定是true的,“==”是false的,但是还真没注意到两个的hashcode是否相等。

           (下面插入代码测试正确)

          

    package equals.hashcode;
    
    public class TestHashcode {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		String a = "what";
    		String b = new String("what");
    		
    		System.out.println(a == b);
    		System.out.println(a.equals(b));
                    System.out.println(a.hashCode() == b.hashCode());
            }
    
    }
    

    输出结果是 false  true true。说明hashcode一样


           后来我查了一下jdk文档,发现对String的hashcode是这样描述的:

    hashCode

    public int hashCode()
    返回此字符串的哈希码。String 对象的哈希码按下列公式计算:
     s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     
    使用 int 算法,这里 s[i] 是字符串的第i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)

     


    覆盖:Object 中的hashCode
    返回:此对象的哈希码值。 另请参见:Object.equals(java.lang.Object),Hashtable

    这说明,String的hashcode其实是对它的字符串的一系列运算,所以只要两个String对象的字符串值是相等的,那么他们的hashcode也是一定相等的。String类被定义为final类型,final类使不能被继承的,因为它的方法不可能被重写。

     

     

    按找文档描述,String的hashcode方法重写了Object的hashcode方法,那么我们再来看看Object的hashcode方法是什么样的:

    hashCode

    public int hashCode()
    返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

    hashCode 的常规协定是:

    • 在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
    • 如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用hashCode 方法都必须生成相同的整数结果。
    • 以下情况 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

    实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

     


    返回:此对象的一个哈希码值。 另请参见:equals(java.lang.Object),Hashtable

     

    Object定义的hashcode方法就是把对象内存地址转换为一个数值,这意味着两个Object必须完全相等(==而不是equals),hashcode才是相等。但是我们再看下,是不是两个对象的hashcode相同,那么他们的equals比较就是true呢?是不是他们的完全比较“==”就是true呢?答案是不一定。这要看这两个对象有没有重写Object的hashCode方法和equals方法。如果没有重写,是按Object默认的方式去处理,Object的equals方法定义如下:

     

     

    equals

    public boolean equals(Object obj)
    指示某个其他对象是否与此对象“相等”。

    equals 方法在非空对象引用上实现相等关系:

    • 自反性:对于任何非空引用值xx.equals(x) 都应返回 true
    • 对称性:对于任何非空引用值xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回true
    • 传递性:对于任何非空引用值xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回true
    • 一致性:对于任何非空引用值xy,多次调用 x.equals(y) 始终返回 true 或始终返回false,前提是对象上 equals 比较中所用的信息没有被修改。
    • 对于任何非空引用值 xx.equals(null) 都应返回false

    Object 类的equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 xy,当且仅当xy 引用同一个对象时,此方法才返回 truex == y 具有值true)。

    注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

     


    参数:obj - 要与之比较的引用对象。返回:如果此对象与 obj 参数相同,则返回 true;否则返回false另请参见:hashCode(),Hashtable

     

    (注意这句话“Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值xy,当且仅当 xy 引用同一个对象时,此方法才返回truex == y 具有值 true) ”。这说明了Obejct定义的equals其实和“==”没什么两样。所以如果对象没有重写Object的hashCode方法和equals方法,那么可以肯定的说,如果两个对象的hashcode相等,那么equals比较必然是true,“==”比较也必然是true。但是如果对象重写了hashCode方法和equals方法,那么情况就不一样了,只能说有可能,但是不能肯定。比如String重写了Object的hashcode和equals,但是两个String如果hashcode相等,那么equals比较肯定是相等的,但是“==”比较却不一定相等。如果自定义的对象重写了hashCode方法,有可能hashcode相等,equals却不一定相等,“==”比较也不一定相等。)

    HashMap是通过链地址法解决hash collision


    主要原因是默认从Object继承来的hashCode是基于对象的ID实现的。
    如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
    这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。



    Map.put(key,value)时根据key.hashCode生成一个内部hash值,根据这个hash值将对象存放在一个table

    Map.get(key)会比较key.hashCodeequals方法,当且仅当这两者相等时,才能正确定位到table;(所以,如果重写了equals方法,使逻辑上相等,但是如果没有重写hashcode方法,在map里面,两个equals的值无法根据其中一个查到另外一个了,因为hashcode不相同。所以,equals的对象一定要hashcode也相同,反过来则不然)

    因为java中默认的hashCode是根据对象的地址计算得到的,虽然p1.equals(p2)=true,但是p1,p1有不同的内存地址,所以有不同的hashCode;所以通过p2是不能得到value的,这个时候value==null;添加hashCode()后,两个对象有相同hashCode,所以能得到

    javaSet是通过Map实现的,所以MapSet的所有实现类都要注意这一点

    HashMap是通过链地址法解决hash collision的,并且新对象都是添加到表头的(这个看了好久才明白,数据结构都还老师了)


    三 总结自Effective Java 的知识点

       

    以下内容总结自《Effective Java》。

    1.何时需要重写equals()

    当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。

    2.设计equals()

    [1]使用instanceof操作符检查“实参是否为正确的类型”。

    [2]对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。

    [2.1]对于非float和double类型的原语类型域,使用==比较;

    [2.2]对于对象引用域,递归调用equals方法;

    [2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;

    [2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较;

    [2.5]对于数组域,调用Arrays.equals方法。

    3.当改写equals()的时候,总是要改写hashCode()

    根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。

    4.设计hashCode()

    [1]把某个非零常数值,例如17,保存在int变量result中;

    [2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):

    [2.1]boolean型,计算(f ? 0 : 1);

    [2.2]byte,char,short型,计算(int);

    [2.3]long型,计算(int) (f ^(f>>>32));

    [2.4]float型,计算Float.floatToIntBits(afloat);

    [2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];

    [2.6]对象引用,递归调用它的hashCode方法;

    [2.7]数组域,对其中每个元素调用它的hashCode方法。

    [3]将上面计算得到的散列码保存到int变量c,然后执行 result=37*result+c;

    [4]返回result。

    5.示例

    下面的这个类遵循上面的设计原则,重写了类的equals()hashCode()

    package com.zj.unit;
    
    import java.util.Arrays;
    
     
    
    public class Unit {
    
        private short ashort;
    
        private char achar;
    
        private byte abyte;
    
        private boolean abool;
    
        private long along;
    
        private float afloat;
    
        private double adouble;
    
        private Unit aObject;
    
        private int[] ints;
    
        private Unit[] units;
    
     
    
        public boolean equals(Object o) {
    
           if (!(o instanceof Unit))
    
               return false;
    
           Unitunit = (Unit) o;
    
           return unit.ashort == ashort
    
                  &&unit.achar == achar
    
                  &&unit.abyte == abyte
    
                  &&unit.abool == abool
    
                  &&unit.along == along
    
                  &&Float.floatToIntBits(unit.afloat) ==Float
    
                         .floatToIntBits(afloat)
    
                  &&Double.doubleToLongBits(unit.adouble) ==Double
    
                         .doubleToLongBits(adouble)
    
                  && unit.aObject.equals(aObject)
    
    &&equalsInts(unit.ints)
    
                  &&equalsUnits(unit.units);
    
        }
    
     
    
        private boolean equalsInts(int[] aints) {
    
           return Arrays.equals(ints,aints);
    
        }
    
     
    
        private boolean equalsUnits(Unit[] aUnits) {
    
           return Arrays.equals(units,aUnits);
    
        }
    
     
    
        public int hashCode() {
    
           int result = 17;
    
           result= 37 * result + (int) ashort;
    
           result= 37 * result + (int) achar;
    
           result= 37 * result + (int) abyte;
    
           result= 37 * result + (abool ? 0 : 1);
    
           result= 37 * result + (int) (along ^ (along >>> 32));
    
           result= 37 * result + Float.floatToIntBits(afloat);
    
           long tolong = Double.doubleToLongBits(adouble);
    
           result= 37 * result + (int) (tolong ^ (tolong>>> 32));
    
           result= 37 * result + aObject.hashCode();
    
           result= 37 * result + intsHashCode(ints);
    
           result= 37 * result + unitsHashCode(units);
    
           return result;
    
        }
    
     
    
        private int intsHashCode(int[] aints) {
    
           int result = 17;
    
           for (int i = 0; i < aints.length;i++)
    
               result= 37 * result + aints[i];
    
           return result;
    
        }
    
     
    
        private int unitsHashCode(Unit[] aUnits) {
    
           int result = 17;
    
           for (int i = 0; i < aUnits.length;i++)
    
               result= 37 * result + aUnits[i].hashCode();
    
           return result;
    
        }
    
    }


  • 相关阅读:
    女程序员这么少是因为怕秃头?如果你这样想,那就错了...
    使用简单的c#示例的坚实的架构原则
    第1部分设计模式FAQ(培训)
    为什么微软部分类和Java不?
    现实世界四部分类和部分方法的使用
    回到基础:n层ASP的异常管理设计指南。网络应用
    学习c#(第9天):理解c#中的事件(一种见解)
    EventBroker:同步和异步通知组件,松散耦合的事件处理
    潜水在OOP(第一天):多态和继承(早期绑定/编译时多态)
    学习c#(第8天):c#中的索引器(一种实用方法)
  • 原文地址:https://www.cnblogs.com/allenzhaox/p/3201830.html
Copyright © 2020-2023  润新知