【问题标题】:what happen when override hashcode()覆盖 hashcode() 时会发生什么
【发布时间】:2015-01-15 05:42:41
【问题描述】:

我对哈希码有些困惑。据我所知,对象类哈希码代表任何对象的内存位置,并且对于每个对象(不同的对象),此哈希码都是唯一的。好的,这一点非常简单。

但是在散列(HashSet、HashMap 等)的情况下,我们需要重写 hashcode 和 equals 方法。现在在这种情况下,哈希码将代表存储桶的位置。现在我有几个问题:

例如,我正在学习Student 类,并且我覆盖了 Student 类的 hashcode() 方法..

class Student{
   //
   int id;
   @override
   int hashcode(){
      return id*31;
   }
}

Student stu1 = new Student(1);
Student stu2 = new Student(1);

如果我创建两个返回相似哈希码的相同 Student 对象(stu1、stu2)。因此,如果我们将这两个对象添加到 hashset 中,它将调用它的 hashcode 并获取存储桶位置,但是真正的学生(我们创建的两个相似对象)对象会发生什么?意味着它在堆中的内存位置是什么?都指向同一个堆位置吗?

【问题讨论】:

  • “众所周知,Object 类哈希码代表任何对象和每个对象的内存位置” - 这实际上不是真的,默认 hashcode 可能会返回一个代表内存位置的哈希值,但是返回任何你想要的东西都是合法的,只要你保持 equalshashcode 之间的合同
  • “都指向同一个堆位置吗?” - 不。第一个对象会被第二个对象碰撞并替换,所以桶现在只包含第二个对象引用...
  • @MichaelGoldstein 那么如果我们重写 hascode 那么有没有办法获取对象的内存位置?
  • @MadProgrammer 第一个对象将被第二个对象替换我相信这两个对象都会在堆上。具有相同的哈希码并不意味着它们引用垃圾收集堆上的相同对象。在散列集合中,它意味着在同一个桶中。我没有看到任何替换发生。
  • @MeenaChaudhary 他说的是桶中的替换而不是堆中的替换。

标签: java hashcode


【解决方案1】:

HashCode 永远不会确定堆中的位置。 equals 和 hashcode 都用于比较对象的相等性,主要是在基于哈希的集合中,例如 Hashtable 和 HashMap。

(绝对没有办法找出内存位置。)

【讨论】:

    【解决方案2】:

    如您所知,Set 实现了equals()hashcode() 方法,

    1. 所以如果hashcode 对两个对象不同,那么集合中就会有两个元素
    2. 如果Hashcode 相等,则第二个条目替换第一个条目。所以集合中只有一个元素

    当两个不相等的对象具有相同的哈希值时,这会导致哈希表中的冲突,因为两个objects 都希望在同一个槽(桶)中。哈希算法必须解析这样的collisions

    Student stu1 = new Student(1);
    Student stu2 = new Student(1);
    

    当您将stu1stu2 添加到具有相同值到set 的集合中时,

    Set<Student> sset=new HashSet<Student >();    
    sset.add(stu1);
    sset.add(stu2);
    

    集合sset 将保存两个具有相同值的值,但它们都引用两个不同的对象,因为它们的hashcode 不同

    【讨论】:

      【解决方案3】:

      嗯...不难看出它们是否共享相同的内存位置。参考平等测试,即 stu1==stu2 将回答这个问题。至于哈希,它们会落入同一个桶中,因为它们具有相同的哈希码。因此,在这种情况下,当您尝试检索学生时,将使用 equals 方法来确定正在查找的学生。运行下面的代码。

      import java.util.HashSet;
      
      class Student {
          //
          int id;
      
          Student(int id) {
              this.id = id;
          }
      
          @Override
          public int hashCode() {
              return id * 31;
          }
      
          public static void main(String[] args) {
              Student stu1 = new Student(1);
              Student stu2 = new Student(1);
              Student stu3 = stu1;
              HashSet<Student> hash = new HashSet<Student>();
              hash.add(stu1);
              hash.add(stu2);
      
              if(stu1==stu2){
                  System.out.println("Stu1 and stu2 refer to the same 
      location in memory...");
              }
      
              if(stu1==stu3){
                  System.out.println("Stu1 and stu3 refer to the same 
      location in memory...");
              }
      
          }
      
      }
      

      【讨论】:

        【解决方案4】:

        对象的哈希码不是位于堆中的对象的物理内存位置。它将存储在类实例中的数据摘要转换为单个哈希值(32 位有符号整数)。 (来源wiki

        But in case of hashing(HashSet, HashMap etc), we need to override hashcode and equals method

        是的,除非您在任何地图实现的类中使用您的类实例作为键。

        现在考虑你给定的场景:

        @override
        int hashcode(){
          return id*31;
        }
        

        什么都不会发生,但您最终会在地图或场景中添加不同的对象。

        重要合同:

        对象hashcodeequals方法之间有一个约定,当你覆盖equals时,你必须覆盖hascode(),反之亦然,以便在桶中均匀分布对象。

        小心!!

        // The worst possible legal hash function - never use!
        
        @Override 
        public int hashCode() { 
        return 42; 
        }
        
        (Source Effective Java)
        

        这是非常合法的,因为它确保相等的对象具有相同的哈希码。这很糟糕,因为它确保每个对象都具有相同的哈希码。因此,每个对象都散列到同一个桶中,散列表退化为链表。应该以线性时间运行的程序改为以二次时间运行。对于大型哈希表,这就是工作和不工作的区别。

        为了您的澄清:

        考虑到这一点,Bellow 是我的班级,在这里我定义了 equals 和 hascode 之间的约定,表示具有相同滚动和名称的两个对象必须相同。

        public class MyClass {
          private int roll;
          private String name;
          private String subject;
        
        public MyClass(int roll, String name, String subject) {
            this.roll = roll;
            this.name = name;
            this.subject = subject;
        }
        
        @Override
        public boolean equals(Object object) {
            boolean result = false;
            if (object == null || object.getClass() != getClass()) {
                result = false;
            } else {
                MyClass myclass = (MyClass) object;
                if (this.roll == myclass.getRoll()
                        && this.name == myclass.getName()) {
                    result = true;
                }
            }
            return result;
        }
        
        @Override
        public int hashCode() {
            int hash = 17;
            hash = 31 * this.roll;
            hash = 31 * hash + this.name.hashCode();
            return hash;
        }
        
        public static void main(String args[]) {
            MyClass obj1 = new MyClass(1, "A", "Math");
            MyClass obj2 = new MyClass(1, "A", "Phy");
            MyClass obj3 = new MyClass(1, "B", "Chem");
            System.out.println("obj1.equals(obj2) : "+obj1.equals(obj2));  // true. As both the objects have the same roll & name
            System.out.println("obj1 == obj2 : "+(obj1 == obj2));   // false. because two are references of different instance.
            Set<MyClass> set = new HashSet<MyClass>();
            set.add(obj1);
            set.add(obj2);
            System.out.println("set :"+set.size()); // 1 object 
            for(MyClass cls:set){
            System.out.println(cls); //i.e [1 A Math]. It'll not replaced by the 2nd one.
            }
            Map<MyClass,String> map= new HashMap<MyClass,String>();
            map.put(obj1,"IN");
            map.put(obj2,"US");
            System.out.println("map :"+map.size());// 1 object
        
            for (Map.Entry<MyClass, String> entry : map.entrySet()){
                System.out.println(entry.getKey() + " : " + entry.getValue());   // [1 A Math : US]. here you may notice the key remains same but the value will be replaced.
            }
        
        }
        
        public int getRoll() {
            return roll;
        }
        
        public String getName() {
            return name;
        }
        
        public String getSubject() {
            return subject;
        }
        @Override
        public String toString(){
            return ""+roll+" "+name+" "+subject;
        }
        

        }

        注意: 每当您使用new 关键字创建对象时,无论其内容是否相同,每次都会在堆中创建不同的对象。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-22
          • 1970-01-01
          相关资源
          最近更新 更多