【问题标题】:Java hashCode from one field来自一个字段的 Java hashCode
【发布时间】:2011-04-01 23:58:22
【问题描述】:

编辑:准备我的对象以在 HashMap 中使用。

在阅读了一些关于如何生成哈希码的信息后,我现在有点困惑。我的(可能是微不足道的)问题是,当我有一个可以使用的字段时,我应该如何实现 hashCode 方法?我可以直接使用字段吗? 如果我理解正确,hashCode 的值在对象的生命周期内不得更改,并且我只有一个适合此的 ID 文件,但我在其他地方读过,不应该使用 ID ...尽管如此,如何基于这个(唯一且不变的)值的 hashCode 函数会是什么样子? equals 方法也是基于 id 而已..

【问题讨论】:

  • @poly 如果我理解正确,比如 IIRC,如果我没记错的话。
  • IIUC 歧义解决为“如果我没记错的话”。

标签: java hashcode


【解决方案1】:

如果您的对象是可变的,那么它的哈希码随时间变化是可以接受的。当然,您应该更喜欢不可变对象(Effective Java 2nd Edition,Item 15:Minimize mutability)。

这是来自 Josh Bloch 的哈希码配方,来自Effective Java 2nd Edition,第 9 项:当您覆盖 equals 时,请始终覆盖 hashCode

Effective Java 2nd Edition hash code recipe

  • 在名为resultint 变量中存储一些恒定的非零值,例如17。
  • 为每个字段计算int 哈希码c
    • 如果字段是boolean,则计算(f ? 1 : 0)
    • 如果字段是byte, char, short, int,则计算(int) f
    • 如果字段是long,则计算(int) (f ^ (f >>> 32))
    • 如果字段是float,则计算Float.floatToIntBits(f)
    • 如果字段是double,计算Double.doubleToLongBits(f),然后像上面一样散列结果long
    • 如果该字段是对象引用,并且此类的equals 方法通过递归调用equals 来比较该字段,则在该字段上递归调用hashCode。如果该字段的值为null,则返回0。
    • 如果该字段是一个数组,则将其视为每个元素都是一个单独的字段。如果数组字段中的每个元素都很重要,您可以使用 1.5 版中添加的 Arrays.hashCode 方法之一。
  • 将哈希码c组合成result如下:result = 31 * result + c;

按原样遵循配方是正确的,即使只有一个字段。只需根据字段的类型执行适当的操作即可。

请注意,有些库实际上可以为您简化此过程,例如HashCodeBuilder 来自Apache Commons Lang,或者只是Arrays.hashCode/deepHashCode 来自java.util.Arrays

这些库允许您简单地编写如下内容:

@Override public int hashCode() {
    return Arrays.hashCode(new Object[] {
        field1, field2, field3, //...
    });
}

Apache Commons 语言示例

这是一个更完整的示例,它使用 Apache Commons Lang 的构建器来促进方便且易读的 equalshashCodetoStringcompareTo

import org.apache.commons.lang.builder.*;

public class CustomType implements Comparable<CustomType> {
    // constructors, etc
    // let's say that the "significant" fields are field1, field2, field3
    @Override public String toString() {
        return new ToStringBuilder(this)
            .append("field1", field1)
            .append("field2", field2)
            .append("field3", field3)
                .toString();
    }
    @Override public boolean equals(Object o) {
        if (o == this) { return true; }
        if (!(o instanceof CustomType)) { return false; }
        CustomType other = (CustomType) o;
        return new EqualsBuilder()
            .append(this.field1, other.field1)
            .append(this.field2, other.field2)
            .append(this.field3, other.field3)
                .isEquals();
    }
    @Override public int hashCode() {
        return new HashCodeBuilder(17, 37)
            .append(field1)
            .append(field2)
            .append(field3)
                .toHashCode();
    }
    @Override public int compareTo(CustomType other) {
        return new CompareToBuilder()
            .append(this.field1, other.field1)
            .append(this.field2, other.field2)
            .append(this.field3, other.field3)
                .toComparison();
    }
}

众所周知,这四种方法编写起来非常乏味,而且很难确保遵守所有合同,但幸运的是,库至少可以帮助简化工作。一些 IDE(例如 Eclipse)也可以为您自动生成其中一些方法。

另见

【讨论】:

  • +1 获取有关哈希码和 Arrays.hashCode() 建议的信息丰富的答案;我第一次看到它,我喜欢它!
  • 再次感谢这个冗长且易于理解的答案。但我错过了其他一点——目前,我不必实现 hashCode(见 Michaels 评论),但也许我将来必须这样做——所以你的答案只是及时。 ;)
  • 从Java SE 7开始,你可以使用Objects#hash(Object...)
  • 请添加我们需要将每个先前的计算值哈希相乘的原因,这是一个很好的信息。
【解决方案2】:

如果您希望通过该 id 识别具有不同 id 的对象,您需要做的就是返回/比较它。

private final int id;

public int hashCode() { return id; }

public boolean equals(Object o) { 
    return o instanceof ThisClass && id == ((ThisClass)o).id;
}

【讨论】:

  • 你的 equals 函数没有以正确的方式实现,只是将 id 作为 hashValue 返回我算作我的问题的答案,因此 +1。
  • equals() 有什么问题?如果只有 id 字段就足以覆盖 hashCode(),那么 Peters equals() 的实现似乎就完美了。
  • @krmby 嗯,afaik 必须进行空检查,以便在这种情况下返回 false。
  • instanceof 会为你做空检查。这 equals() 很难看,但似乎是正确的。
【解决方案3】:

使用多少字段来计算hashCode并不重要。 但是使用 equals() 很重要。如果 A 等于 B,则它们的 hashCode 必须相同。

如果你讨厌 hashCode:) 并且你的对象永远不会被放入基于散列的容器(HashMap、HashSet..),那么就不要管 hashCode(),让它的基类来计算 hashCode。

【讨论】:

  • 好点,你不需要为大多数对象实现equals/hashcode。
  • 我问是因为我现在使用 HashMap 来获取获取条目的 O(1) 时间。
  • @InsertNickHere:抱歉误读了您的问题。确保将您的对象作为 KEY 而不是值放入 HashMap。
  • 哦,看,你指出我完全搞错了。认为如果我使用我的类作为值,我必须编写哈希码 evan。 D'oh,我能有多愚蠢? ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-11
  • 1970-01-01
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-12
相关资源
最近更新 更多