我们怎么决定最好的执行情况 hashCode() 方法收集(假定平等的方法已经复盖正确地)?

有帮助吗?

解决方案

最好的执行?这是一个很难的问题,因为它取决于使用模式。

一个为几乎所有情况下合理的良好的执行提议 乔希*布洛克's 有效的Java 在项目8(第二版)。最好的事情就是看看它有因为提交人解释那里为什么办法是良好的。

一个简短的版本

  1. 创建一个 int result 并分配 非零 值。

  2. 对于 每一场 f 测试 equals() 方法,计算的散列代码 c 通过:

    • 如果领域是一个f boolean:计算 (f ? 0 : 1);
    • 如果领域是一个f byte, char, shortint:计算 (int)f;
    • 如果领域是一个f long:计算 (int)(f ^ (f >>> 32));
    • 如果领域是一个f float:计算 Float.floatToIntBits(f);
    • 如果领域是一个f double:计算 Double.doubleToLongBits(f) 并处理回返的价值就像每一个长期价值;
    • 如果领域是一个f 对象:使用结果 hashCode() 方法或如果0 f == null;
    • 如果领域是一个f 阵列:看到每一领域作为独立元件和计算的散列值中的一个 recursive时尚 并组合的价值如下。
  3. 结合起来的散列值 cresult:

    result = 37 * result + c
    
  4. 返回 result

这应该导致一个适当的分散列值的大多数使用情况。

其他提示

如果您对dmeister推荐的Effective Java实现感到满意,可以使用库调用而不是自己编译:

@Override
public int hashCode() {
    return Objects.hashCode(this.firstName, this.lastName);
}

这需要Guava( com.google.common.base.Objects.hashCode )或Java 7中的标准库( java.util.Objects.hash )但工作方式相同。

最好使用Eclipse提供的功能,这项功能非常出色,您可以将精力和精力投入到开发业务逻辑中。

虽然这与 Android 文档(Wayback Machine)我自己在Github上的代码,它一般适用于Java。我的回答是 dmeister's Answer 的扩展,其中的代码更易于阅读和理解。

@Override 
public int hashCode() {

    // Start with a non-zero constant. Prime is preferred
    int result = 17;

    // Include a hash for each field.

    // Primatives

    result = 31 * result + (booleanField ? 1 : 0);                   // 1 bit   » 32-bit

    result = 31 * result + byteField;                                // 8 bits  » 32-bit 
    result = 31 * result + charField;                                // 16 bits » 32-bit
    result = 31 * result + shortField;                               // 16 bits » 32-bit
    result = 31 * result + intField;                                 // 32 bits » 32-bit

    result = 31 * result + (int)(longField ^ (longField >>> 32));    // 64 bits » 32-bit

    result = 31 * result + Float.floatToIntBits(floatField);         // 32 bits » 32-bit

    long doubleFieldBits = Double.doubleToLongBits(doubleField);     // 64 bits (double) » 64-bit (long) » 32-bit (int)
    result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));

    // Objects

    result = 31 * result + Arrays.hashCode(arrayField);              // var bits » 32-bit

    result = 31 * result + referenceField.hashCode();                // var bits » 32-bit (non-nullable)   
    result = 31 * result +                                           // var bits » 32-bit (nullable)   
        (nullableReferenceField == null
            ? 0
            : nullableReferenceField.hashCode());

    return result;

}

修改

通常,当您覆盖 hashcode(...)时,您还要覆盖 equals(...)。那么对于那些已经或已经实现 equals 的人来说,这是一个很好的参考来自我的Github ...

@Override
public boolean equals(Object o) {

    // Optimization (not required).
    if (this == o) {
        return true;
    }

    // Return false if the other object has the wrong type, interface, or is null.
    if (!(o instanceof MyType)) {
        return false;
    }

    MyType lhs = (MyType) o; // lhs means "left hand side"

            // Primitive fields
    return     booleanField == lhs.booleanField
            && byteField    == lhs.byteField
            && charField    == lhs.charField
            && shortField   == lhs.shortField
            && intField     == lhs.intField
            && longField    == lhs.longField
            && floatField   == lhs.floatField
            && doubleField  == lhs.doubleField

            // Arrays

            && Arrays.equals(arrayField, lhs.arrayField)

            // Objects

            && referenceField.equals(lhs.referenceField)
            && (nullableReferenceField == null
                        ? lhs.nullableReferenceField == null
                        : nullableReferenceField.equals(lhs.nullableReferenceField));
}

首先确保平等是实现正确的。从 IBM发现的文章:

  • 对称性:对两个参考,a和b,一个.等于(b)如果并且只有如果b。等于(a)
  • 反思:对所有非空引用。等于(a)
  • 传递:如果一个.等于(b)和b。等于(c),然后一个.等于(c)

然后确保它们之间的关系,与哈希码方面的接触(从同一条):

  • 一致性与哈希码():两个相等的对象必须拥有相同哈希码()价值

最后一个良好的散列函数应该努力的方法的 理想的散列函数.

about8.blogspot.com,你说

  

如果equals()为两个对象返回true,则hashCode()应该返回相同的值。如果equals()返回false,则hashCode()应返回不同的值

我不能同意你的看法。如果两个对象具有相同的哈希码,则不一定意味着它们是相等的。

如果A等于B,那么A.hashcode必须等于B.hascode

如果A.hashcode等于B.hascode,则并不意味着A必须等于B

如果你使用eclipse,你可以使用:

生成 equals() hashCode()
  

来源 - >生成hashCode()和equals()。

使用此函数,您可以决定要用于等式和哈希码计算的字段,Eclipse会生成相应的方法。

只是一个快速的注意完成其它更详细的回答中(在任期的代码):

如果我考虑的问题 怎么做-我-创建一个hash-table在java 尤其是 jGuru FAQ entry, 我认为一些其他标准在其散列代码可以被审判是:

  • 同步(不algo支持并行访问或不)?
  • 失败的安全迭代(不算法检测的一系列变更迭代期间)
  • 空值(不列码支持空值在收集)

如果我理解你的问题,你有一个自定义集合类(即从Collection接口扩展的新类),你想实现hashCode()方法。

如果您的集合类扩展了AbstractList,那么您不必担心它,已经有一个equals()和hashCode()的实现,它通过迭代所有对象并将它们的hashCodes()一起添加来工作。

   public int hashCode() {
      int hashCode = 1;
      Iterator i = iterator();
      while (i.hasNext()) {
        Object obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
      }
  return hashCode;
   }

现在,如果你想要的是计算特定类的哈希码的最佳方法,我通常使用^(按位异或)运算符来处理我在equals方法中使用的所有字段:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}

@ about8:那里有一个非常严重的错误。

Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");

相同的哈希码

你可能想要像

这样的东西
public int hashCode() {
    return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();

(这些天你能直接从Java中获取hashCode吗?我认为它会进行一些自动排序..如果是这种情况,请跳过toString,这很难看。)

正如您特别要求收集的那样,我想添加一个其他答案尚未提及的方面:HashMap不希望他们的密钥在添加到集合后更改其哈希码。会打败整个目的......

在Apache Commons上使用反射方法 EqualsBuilder HashCodeBuilder

任何散列方法都可以在可能的范围内均匀分布散列值,这是一个很好的实现。查看有效的java( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa= X& oi = book_result& resnum = 1& ct = result ),有一个很好的提示,用于哈希码实现(我认为第9项)。

我更喜欢使用来自对象的 Google Collections lib中的实用程序方法来帮助我保持代码清洁。通常 equals hashcode 方法都是从IDE的模板制作的,因此它们不易读取。

我在 Arrays.deepHashCode(...) 因为它正确处理作为参数提供的数组

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}

这是另一个JDK 1.7+方法演示,其中包含超类逻辑。我认为它非常方便Object类hashCode()占用,纯JDK依赖,没有额外的手工工作。请注意 Objects.hash()是空容忍的。

我没有包含任何 equals()实现,但实际上你当然需要它。

import java.util.Objects;

public class Demo {

    public static class A {

        private final String param1;

        public A(final String param1) {
            this.param1 = param1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param1);
        }

    }

    public static class B extends A {

        private final String param2;
        private final String param3;

        public B(
            final String param1,
            final String param2,
            final String param3) {

            super(param1);
            this.param2 = param2;
            this.param3 = param3;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param2,
                this.param3);
        }
    }

    public static void main(String [] args) {

        A a = new A("A");
        B b = new B("A", "B", "C");

        System.out.println("A: " + a.hashCode());
        System.out.println("B: " + b.hashCode());
    }

}

标准实现很弱,使用它会导致不必要的冲突。想象一下

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

现在,

new ListPair(List.of(a), List.of(b, c))

new ListPair(List.of(b), List.of(a, c))

具有相同的 hashCode ,即 31 *(a + b)+ c ,因为 List.hashCode 的乘数在这里被重用。显然,碰撞是不可避免的,但产生不必要的碰撞只是......不必要。

使用 31 并没有什么明智之处。乘数必须是奇数,以避免丢失信息(任何偶数乘数至少失去最高有效位,四倍数减去两个,等等)。任何奇数乘数都是可用的。小乘数可能会导致更快的计算(JIT可以使用移位和加法),但考虑到乘法在现代Intel / AMD上只有三个周期的延迟,这几乎不重要。小乘数也会导致小输入的更多碰撞,这有时可能是一个问题。

使用素数是没有意义的,因为素数在环Z /(2 ** 32)中没有意义。

所以,我建议使用一个随机选择的大奇数(随意取一个素数)。由于i86 / amd64 CPU可以使用更短的指令来操作适合单个有符号字节的操作数,因此像109这样的乘法器有一个很小的速度优势。为了最大限度地减少冲突,请使用类似0x58a54cf5的内容。

在不同的地方使用不同的乘数是有帮助的,但可能不足以证明其他工作的合理性。

组合哈希值时,我通常使用boost c ++库中使用的组合方法,即:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

这确保了均匀分布的相当好。有关此公式如何工作的一些讨论,请参阅StackOverflow帖子: boost :: hash_combine <中的幻数/ A>

有关不同哈希函数的讨论很好: http://burtleburtle.net/bob /hash/doobs.html

对于一个简单的类,通常最容易根据equals()实现检查的类字段实现hashCode()。

public class Zam {
    private String foo;
    private String bar;
    private String somethingElse;

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        Zam otherObj = (Zam)obj;

        if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
            if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
                return true;
            }
        }

        return false;
    }

    public int hashCode() {
        return (getFoo() + getBar()).hashCode();
    }

    public String getFoo() {
        return foo;
    }

    public String getBar() {
        return bar;
    }
}

最重要的是保持hashCode()和equals()一致:如果equals()为两个对象返回true,则hashCode()应该返回相同的值。如果equals()返回false,则hashCode()应该返回不同的值。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top