大家先看完有关hashcode的介绍再看看我的例子程序,麻烦大家了。这问题困扰小弟多天了。有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,我来解释一下吧。
    首先,想要明白hashCode的作用,你必须要先知道Java中的集合。    总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。    于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。    这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。    所以,Java对于eqauls方法和hashCode方法是这样规定的:
      1、如果两个对象相同,那么它们的hashCode值一定要相同;
      2、如果两个对象的hashCode相同,它们并不一定相同    上面说的对象相同指的是用eqauls方法比较。
    你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。程序:
import java.util.*;public class TestHashCode1{
    public static void main(String[] args) {
Name n1=new Name("f1","n1");
Name n2=new Name("f1","n1");
String s1=new String("w");
String s2=new String("w");
System.out.println(n1.hashCode());
System.out.println(n2.hashCode());//结果n1和n2的hashcode并不相同
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());//s1和s2的hashcode相同
System.out.println(n1.equals(n2));//true
Collection c = new HashSet();
        c.add("hello");
        c.add(new Name("f1","l1"));
        c.add(new Integer(100));
        c.remove("hello"); 
        c.remove(new Integer(100));
        System.out.println
                  (c.remove(new Name("f1","l1")));//false
        System.out.println(c);    }
}class Name  {
    private String firstName,lastName;
    public Name(String firstName, String lastName) {
        this.firstName = firstName; this.lastName = lastName;
    }
  
    public String toString() {  return firstName + " " + lastName;  }
    
    public boolean equals(Object obj) {
    if (obj instanceof Name) {
        Name name = (Name) obj;
        return (firstName.equals(name.firstName))
            && (lastName.equals(name.lastName));
    }
    return super.equals(obj);
}
//public int hashCode() {
//   return firstName.hashCode();
//}

}
这程序中,开始我并没有重写Name的hashCode方法,结果如注释所示,大家可以看到n1和n2这两个对象相同(equals)但它们的hashcode不同。所以并没有删除New Name("f1","l1")。
这里就跟上面说的矛盾了
 1、如果两个对象相同,那么它们的hashCode值一定要相同;当我重写hashCode方法后,返回firstName.hashCode()强行使n1和n2的hashcode的返回值相同后,就成功删除了
New Name("f1","l1")这种方法使我不能接受!
发现一个问题,如果不重写Name的toString方法,直接打印n1的话@后面的就是n1的hashcode值的十六进制!
麻烦高手解答小弟疑惑!
不慎感激!

解决方案 »

  1.   

    你自己查得很明确了。
    两个不同的对象,hashcode不同。
    String a = new String("abc");
    String b = new String("abc");
    两个不同的对象,但是值相同。hashcode用于判断对象的地址是否相同,而不是值。
      

  2.   

    Name n1=new Name("f1","n1"); 
    Name n2=new Name("f1","n1"); 
    n1和n2不是两个相同的对象,只是他们的值相同。地址是不同的,所以equal相同,hashcode是不同的。
      

  3.   

    可能我没说清楚我的问题,那s1和s2也是不同的对象为什么它们的hashcode的值相同呢?
      

  4.   

    equals()相等的两个对象,hashcode()一定相等; 
    反过来:hashcode()不等,一定能推出equals()也不等; 
    hashcode()相等,equals()可能相等,也可能不等。 
      

  5.   

    如果你打印一个对象,不重些toString()方法,它就会调用默认你的toString()打印这个些的类名加这个这个类名的十六十六进制。
      

  6.   

    理解存在偏差。
    “要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是 Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。”
    Java实现者怎么可能笨到这种地步。内部怎么实现那可是一门深奥的学问呢,设计很多高级的数据结构。否则还要用它提供的Collection干什么!自己也写一个呗。“于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。”
    这个理解确实荒唐,按这种说法Hash在不同的电脑上对同一个字符串难道还会返回不同的值,移植性从何谈起。
      

  7.   

    赞,楼主真细心!我举例子的时候,因为偷懒,不恰当的使用了String类。String这个类在java中是一个很特殊的类。因为在一般情况下,大部分代码都在做字符串的操作。而同时保存很多字符串又需要消耗大量内存。实际中,我们的程序中会出现很多重复的字符串资源。针对这样的情况,java的虚拟机实现中,对String这个类做了特殊处理。在内存中又一个部分被称作“字符串”池。程序中的字符串被保存在这个池中,值相同的字符串在池中只保留一份。这样大量的减少了字符串存储的所需空间。也就是说:
    String s1 = new String("a ");
    String s2 = new String("a ");
    这样的代码,实际上在内存中,只有在字符串池中,保存一个"a "这样的字符串。s1和s2是这个字符串的两个引用。因为同一个字符串被多个引用,如果一个引用修改这个值,会导致所有引用的值都改变。这样就使错误的。为了避免这样的问题出现。java的实现中,字符串池中的字符串具有不变性。也就是说,一点字符串创立,那么他在jvm中永远不会改变。
    比如:
    String s1 = new String("a ");//a空格
    String s2 = new String("a ");s1=s1.trim();
    这个操作实际上是生成了一个新的字符串保存s1.trim()方法的结果"a",然后将s1的引用更新为这个新字符串。
    这是这个特性,也导致类s1+s2的时候,实际上是产生了一个新的字符串,保存着s1+s2的值。也就是为什么做java的程序员会被告诫用StringBuffer做字符串连接。后头我们看你的问题。
    String s1 = new String("a ");
    String s2 = new String("a ");
    中,s1和s2的值相同,所以equalse方法为true,因为s1和s2都是通过new获得的,s1与s2是两个类似c中指针的空间,这两个空间由new分配。所以s1与s2并不相等(==为false).
    最后我们说hashCode。这两个字符串的值,实际上在同一空间,即字符串池中的"a ",hashCode代表内存地址的位置,因为在寻找这个值得时候,可以用hashCode定位,做快速的内存操作,所以如果hashCode是s1和s2两个指针的地址,那么根据他定位出来的是两个指针,不能做值得比较。而HashCode实际上保存的时最终的那个池中的"a "的地址,所以s1和s2的hashCode相同。总结下:
    s1.equals(s2);//true
    s1==s2;//false
    因为字符串池的原因s1.hashCode()==s2.hashCode()。有不完整或有疑义的地方,各位补充、探讨。
    感谢楼主!
      

  8.   

    感谢楼上的耐心解答!这问题越来越有意思了!希望更多的朋友进来讨论!
    大家来看看n1和n2 它们equals为true但它们的hashcode不相等,然后c.remove(new Name("f1","l1"))时,它与n1是equals的,但remove方法会先比较它们的hashcode值,显然它们的值并不相等,所以返回fluse。
    当我重写hashCode方法后,返回firstName.hashCode()强行使n1和n2的hashcode的返回值相同后,c.remove(new Name("f1","l1"))返回true了。
      

  9.   

    这个东西关键是弄懂内存,java中定义的类,当你new 对象的时候系统自动为其分配内存,上面的n1和n2 new了两次,所以占用两块内存,其hashcode必然不同;
    java中的String类比较特殊,他和普通类的存储方式不同,他存储时自动进行优化,上面的s1和s2由于内容相同,所以占用一块内存,故其hashcode相同.
      

  10.   

    看来很多人都有这个疑惑,s1和s2并不占用一块内存吧,它们都是new出来的,不可能占用同一块内存
         String s1=new String("w");
    String s2=new String("w");
    String s3="ww";
    String s4="ww";
                    System.out.println(s1.hashCode());
    System.out.println(s2.hashCode());
    System.out.println(s1==s2);//false
    System.out.println(s4==s3);//ture
    s1和s2并不==,但它们的hashcode相等,字符串的hashcode是这样计算的:
    String 对象的哈希码按下列公式计算: 
     s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。) 
    只是根据字符串的内容而已!也就是只要字符串一致,hashcode的就相等。与是否==无关。请大家仔细研究下这个问题!
    待解决
      

  11.   

    hashCode()是Object类的方法。String类重写了这个方法。所以一般意义上的HashCode的作用与String的不同。这些在API doc中说明了。代码如下,大家注意看注释:)hashCode()方法在Object类中是Native方法,代码如下:    /**
         * Returns a hash code value for the object. This method is 
         * supported for the benefit of hashtables such as those provided by 
         * <code>java.util.Hashtable</code>. 
         * <p>
         * The general contract of <code>hashCode</code> is: 
         * <ul>
         * <li>Whenever it is invoked on the same object more than once during 
         *     an execution of a Java application, the <tt>hashCode</tt> method 
         *     must consistently return the same integer, provided no information 
         *     used in <tt>equals</tt> 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. 
         * <li>If two objects are equal according to the <tt>equals(Object)</tt>
         *     method, then calling the <code>hashCode</code> method on each of 
         *     the two objects must produce the same integer result. 
         * <li>It is <em>not</em> required that if two objects are unequal 
         *     according to the {@link java.lang.Object#equals(java.lang.Object)} 
         *     method, then calling the <tt>hashCode</tt> 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.
         * </ul>
         * <p>
         * As much as is reasonably practical, the hashCode method defined by 
         * class <tt>Object</tt> 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 
         * Java<font size="-2"><sup>TM</sup></font> programming language.)
         *
         * @return  a hash code value for this object.
         * @see     java.lang.Object#equals(java.lang.Object)
         * @see     java.util.Hashtable
         */
        public native int hashCode();
        String类中的hashCode()的实现:
        /**
         * Returns a hash code for this string. The hash code for a
         * <code>String</code> object is computed as
         * <blockquote><pre>
         * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
         * </pre></blockquote>
         * using <code>int</code> arithmetic, where <code>s[i]</code> is the
         * <i>i</i>th character of the string, <code>n</code> is the length of
         * the string, and <code>^</code> indicates exponentiation.
         * (The hash value of the empty string is zero.)
         *
         * @return  a hash code value for this object.
         */
        public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;            for (int i = 0; i < len; i++) {
                    h = 31*h + val[off++];
                }
                hash = h;
            }
            return h;
        }
      

  12.   

    如果想看对象原始的,也就是 Object 所实现的 hashCode 的话,可以使用:
    System.identityHashCode 这个方法,无论该类是否重写过 hashCode 方法都能调用。
      

  13.   

    支持7楼的回答,String是一个引用类型。
    在创建对象的时候,是创建的一个引用。
    而s1,s2他们的值都是一样的。所以2个对象引用的同一个文中说到的物理路径。因而他们的hashCode是相同。
      

  14.   

    楼主搞清楚了这段话:对象相同和对象相等时两个不同的概念 对象相同(先不谈string,因为他重写了equals方法)如:Name a1=new Name("f1","n1")和 Name a2=new Name("f2","n2")他们叫相等,不叫相同,而Name a3=a1,这叫 a3和a1相同,这里的前提是你没有重写equals方法,因为这样你就调用的是object里面的equals方法,object里面的equals方法比较的是两个对象的引用地址是否相等,那当然a1和a2的引用的地址不相等了,因为new了2个空间嘛,而a3和a1是指向同一对象的,所以是相等的。  然而对于String来说由于他重写了equals方法,他的equals比较的是2个对象的值是否相等。所以,String S1=new String(“w”)和String s2=new String("w")euqals是相等的,再记住那几句话(Java对于eqauls方法和hashCode方法是这样规定的: 
          1、如果两个对象相同,那么它们的hashCode值一定要相同; 
          2、如果两个对象的hashCode相同,它们并不一定相同 )
    然后你再结合这边文章看看答案
      

  15.   

    1、如果两个对象相同,那么它们的hashCode值一定要相同;
    2、如果两个对象的hashCode相同,它们并不一定相同 这个应该是你应该去遵守的,而不是写出了一段代码出来发现和你的预期不一致的时候,你想去推翻的。
    只有你遵守了这个约定的时候,Collection和Set等数据结构才可能给你正确的结果。
      

  16.   

    很多人都把String当成一个特例,其实不是的,大家有兴趣可以去看看String类的源代码就明白了。
    Object类中,hashcode方法返回的就是内存地址,所以自定义一个类,如果没重写hashcode方法,则返回的和Object对象一样,是内存地址。
    对于String类来说,因为业务的需要,通常情况下大家认为字符串值相等则是相同的对象,所以在String中重写了hashcode方法,返回的是和字符串值相关的一个整型值。
    对象往SET集中加入值的时候的判断,我想楼主可以突略了一个最大的问题,理解上也是有校大偏差。就是SET集的实现都是基于哈希桶的算法的。
    关于哈希桶简单的原理可以描述为:比如一个HashSet开了10000这么大空间的内存区,HashSet会在使用时把这个内存区分成10个小区(假设,实际上区数是2的N次方), 要往这个HashSet里加对象的时候,首先会判断加入的对象的hashcode值,我们都知道这个值是整型的,取到了这个值后除以区数,也就是除以10,得到作数是几就加入到十个区里的编号为几的区里面。当然要从Set集里取对象也是一样,首先判断要取对象的hashcode值,然后除以10,余数是几,就到编号为几的分区里去找相应的对象。
      

  17.   

    String重写hashcode之后,返回的hashcode实际上是ASCII码