【问题标题】:Hash function for a generic object通用对象的哈希函数
【发布时间】:2012-09-17 19:51:35
【问题描述】:

您如何为通用对象提出哈希函数?如果用户定义的两个对象“相等”,则存在两个对象需要具有相同哈希值的约束。 Java 是如何做到这一点的?

【问题讨论】:

  • 开发者完成了这个。如果开发人员不这样做,Java 将不会更正它们或无法编译。我建议你看看java.lang.*java.util.* 下的类,看看它是如何完成的。

标签: java data-structures hashmap hashtable


【解决方案1】:

我刚刚找到了我自己问题的答案。 Java 的做法是为每个对象定义一个 hashCode,默认情况下,如果两个对象在内存中相同,则两个对象的 hashCode 相同。因此,当哈希表的客户端覆盖对象的 equals() 方法时,他还应该覆盖计算哈希码的方法,这样如果 a.equals(b) 为真,则 a.hashCode() 也必须等于 b.hashCode ()。这样,可以确保相等的对象具有相同的哈希码。

【讨论】:

    【解决方案2】:

    首先,基本上你通过覆盖hashCode() method 来定义一个类的散列函数。 Javadoc 声明:

    hashCode的一般合约是:

    • 只要在 Java 应用程序执行期间对同一个对象多次调用,hashCode 方法必须始终返回相同的整数,前提是没有修改对象上的 equals 比较中使用的信息。该整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。
    • 如果两个对象根据 equals(Object) 方法相等,那么对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
    • 如果根据 equals(java.lang.Object) 方法,如果两个对象不相等,则不要求对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

    所以更重要的问题是:是什么让你的两个对象相等?反之亦然:哪些属性使您的对象独一无二?如果您对此有答案,请创建一个 equals() 方法来比较所有属性,如果它们都相同则返回 true,否则返回 false

    hashCode() 方法有点复杂,我建议你不要自己创建它,而是让你的 IDE 来做。在 Eclipse 中,您可以从菜单中选择 Source,然后选择 Generate hashCode() 和 equals()。这也保证了上述要求。


    这是一个小(简化的)示例,其中使用 Eclipse 生成了这两种方法。请注意,我选择不包含 city 属性,因为 zipCode 已经唯一标识了一个国家/地区内的城市。

    public class Address {
    
        private String streetAndNumber;
        private String zipCode;
        private String city;
        private String country;
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((country == null) ? 0 : country.hashCode());
            result = prime * result
                    + ((streetAndNumber == null) ? 0 : streetAndNumber.hashCode());
            result = prime * result + ((zipCode == null) ? 0 : zipCode.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(final Object obj) {
            if(this == obj)
                return true;
            if(obj == null)
                return false;
            if(!(obj instanceof Address))
                return false;
            final Address other = (Address) obj;
            if(country == null) {
                if(other.country != null)
                    return false;
            }
            else if(!country.equals(other.country))
                return false;
            if(streetAndNumber == null) {
                if(other.streetAndNumber != null)
                    return false;
            }
            else if(!streetAndNumber.equals(other.streetAndNumber))
                return false;
            if(zipCode == null) {
                if(other.zipCode != null)
                    return false;
            }
            else if(!zipCode.equals(other.zipCode))
                return false;
            return true;
        }
    }
    

    【讨论】:

      【解决方案3】:

      Java 不这样做。如果 hashCode() 和 equals() 没有显式实现,JVM 将为有意义相等的实例生成不同的 hashCode。您可以查看 Joshua Bloch 的 Effective Java。真的很有帮助。

      【讨论】:

        【解决方案4】:

        几个选项:

        • 阅读约书亚·布洛赫 (Joshua Bloch) 的《有效 Java》。它包含一个很好的哈希码算法
        • 让您的 IDE 生成 hashCode 方法
        • Java SE 7 及更高版本:使用Objects.hash

        【讨论】:

          【解决方案5】:

          java.lang.Object 类作弊。它将相等性(由equals 确定)定义为对象标识(由== 确定)。因此,除非您在子类中覆盖 equals,否则您的类的两个实例是“相等的”,如果它们碰巧是同一个对象。

          为此相关的哈希码由系统函数System.identityHashCode 实现(它不再真正基于对象地址——曾经是这样吗?——但可以认为是以这种方式实现的)。

          如果您覆盖equals,那么hashCode 的这种实现就不再有意义。

          考虑以下示例:

          class Identifier {
          
              private final int lower;
              private final int upper;
          
              public boolean equals(Object any) {
                  if (any == this) return true;
                  else if (!(any instanceof Identifier)) return false;
                  else {
                      final Identifier id = (Identifier)any;
                      return lower == id.lower && upper == id.upper;
                  }
              }
          }
          

          这个类的两个实例被认为是相等的,如果它们的“lower”和“upper”成员具有相同的值。由于相等现在由对象成员确定,我们需要以兼容的方式定义hashCode

          public int hashCode() {
              return lower * 31 + upper;  // possible implementation, maybe not too sophisticated though
          }
          

          如您所见,我们在hashCode 中使用相同的字段,我们在确定相等性时也会使用这些字段。将哈希码基于所有成员通常是一个好主意,在比较相等性时也会考虑这些代码。

          请考虑以下示例:

          class EmailAddress {
          
              private final String mailbox;
              private final String displayName;
          
              public boolean equals(Object any) {
                  if (any == this) return true;
                  else if (!(any instanceof EmailAddress)) return false;
                  else {
                      final EmailAddress id = (EmailAddress)any;
                      return mailbox.equals(id.mailbox);
                  }
              }
          }
          

          由于这里,相等性仅由mailbox 成员确定,哈希码也应仅基于该成员:

          public int hashCode() {
              return mailbox.hashCode();
          }
          

          【讨论】:

            【解决方案6】:

            对象的散列是通过覆盖hashCode() method 来建立的,开发人员可以覆盖它。

            Java 在默认哈希码计算中使用素数。

            如果equals()hashCode()方法没有实现,JVM会为对象隐式生成hashcode(对于Serializable类,会生成serialVersionUID)。

            【讨论】:

            • 其实这不正确。您不必为不同的对象返回不同的整数,正如 Joshua Bloch 在他的 Effective Java 书中所说,找到一个好的算法仍然是一个研究课题。唯一的要求是为相等的对象返回相同的整数,并且如果对象没有改变,则 hashCode 不应该改变。例如。一个糟糕但正确的实现是返回一个常量值。 (不好,因为一些依赖 hashCode 的算法会退化为迭代列表。)
            • @Puce,所以你告诉我对象AB 可以有相同的hashCode()?对于相等的对象,是的,hashCode 返回相同的值,但该值对于类必须是不同的。
            • 不,不必区分 :-) hashCode 通常用于对“桶”中的对象进行分组。如果所有对象都具有相同的 hashCode,则所有对象都以相同的“桶”结尾。使用 hashCode 是因为比较整数比比较对象更快。但它仅用作预选。将使用 equals 方法比较同一“桶”中的所有对象。如果您有一个好的 hashCode 方法并且 hashCodes 不会偶然发生冲突,那么每个存储桶只有一个对象 -> 非常有效。如果所有对象都返回相同的 hashCode 最终都在同一个“桶”中 -> 遍历列表。
            • 或者来自Javadoc: •如果两个对象根据equals(java.lang.Object)方法不相等,则不要求对两个对象中的每一个调用hashCode方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
            • “hashCode 应该为不同的对象返回一个不同的整数”
            猜你喜欢
            • 1970-01-01
            • 2011-11-28
            • 2010-10-25
            • 1970-01-01
            • 1970-01-01
            • 2013-05-08
            • 2014-05-30
            • 2015-06-16
            • 1970-01-01
            相关资源
            最近更新 更多