“equals any of some fields are equal”的hashCode实现

5
我希望一个特定类的对象如果其中一个字段相等则被视为相等。如何为这样的类编写一致的hashCode方法?
(免责声明,因为我知道这不是最佳实践:该类是另一个类的包装器,并应用于Map中的键。两个具有一个相等字段的不同对象将导致相同的Map条目。实际上,每个字段都可以单独标识基础对象,但我并不总是有相同的标识字段可用于两个对象。我不能控制并因此无法更改这种“模糊”的标识机制。也欢迎其他解决方案。)
是否有实现相应hashCode()的策略?我只知道涉及连接(如&&)的实现方式。如何确保如果任一字段相等,则hashCode()相同?
下面是我想编写一致的hashCode()实现的简化equals方法:
public boolean equals(C other)
{
    return (field1 != null && field1.equals(other.field1))
            || (field2 != null && field2.equals(other.field2))
            || (field3 != null && field3.equals(other.field3));
}

编辑:根据输入数据,不可能出现类似于(1, 2, 3)等于(1, 6, 7)的情况。对象只会产生一些字段为空的情况,但不会像例子中那样矛盾。简单来说,在实践中,唯一等于(1, 2, 3)的组合应该是(1, 2, 3),(1, null, null),(null, 2, null),(1, 2, null)等等。我承认这种方法并不特别强大。


因此,在您的哈希码实现中,只使用该字段。 - Prashant
2
那个相等关系不是传递的。无论如何,你都会违反合同。 - 5gon12eder
如果两个实例中有任何一个字段(成对)相等,则认为这两个实例是“相等”的。 - JayK
有几种方法可以实现这一点,但你应该查看合同以覆盖equals()和hashCode()方法的实现。 - Prashant
1
为什么你需要这个奇怪的equals方法呢?难道你不能定义一个实现这个逻辑的函数对象,并且只在实际需要的地方显式地使用它吗?这样做会使你的代码更清晰,避免未来出现许多令人困惑的错误。 - 5gon12eder
显示剩余3条评论
6个回答

2
您不能使用任何字段来实现hashCode,因为这些字段并不总是相等的。
您的hashCode方法需要始终为相等的对象返回相同的值。由于您的equals方法中只需要一个字段是相等的,而且它并不总是相同的,您唯一的选择就是在hashCode方法中返回一个常量。
这种实现虽然效率低下,但是它是有效的,并且与equals方法一致。
具体的实现方式可以是:
public int hashCode() {
    return 0;
}

1
OP可能在询问是否可以使用“仅一个字段”来实现equals()和hashCode()。 - ha9u63a7
1
在这种情况下,答案是否定的。Equals() 可以在 field3 上返回 true,但 hasCode() 可能是基于 field1 生成的。 - TheLostMind
1
@ha9u63ar 谢谢,我误解了问题,我更新了我的答案。 - David SN

1

通常情况下,不建议仅使用一个字段来实现equals()hashCode()。一般做法是确保比较所有字段并确保它们全部相等才能调用.equals()hashCode()使用.equals()对这些对象进行哈希。但是,如果您可以控制自己的操作,可以仅使用对象中特定字段的hashCode(),并基于此重写equals().hashCode()(但再次强调,不建议这样做)。


1

看起来唯一的解决方案是这个

public int hashCode() {
    return 1;
}

实际上,我还能想到2^32种解决方案...第(2^32 + 1)个解决方案是抛出异常。 - 5gon12eder
1
@Prashant - 这是正确的解决方案。equality合同本身就有缺陷,不要期望hashCode()没有缺陷。 - TheLostMind
确实,这样做可以工作,但它也会导致HashMap退化,从而降低性能,不是吗? - JayK
1
如果您将“equals”实现为不是等价关系,则Java集合框架无论如何都会退出。 - 5gon12eder
@TheLostMind 我得到了 :D - Prashant
@JayK - HashMap中有一个补充的hash()方法,用于处理较差的哈希码 :P - TheLostMind

1
问题中的equals()的实现。
  public boolean equals(C other) {
    //TODO: you have to check if other != null...

    return (field1 != null && field1.equals(other.field1)) ||
           (field2 != null && field2.equals(other.field2)) ||
           (field3 != null && field3.equals(other.field3));
  }

这是一个错误的示例。在实现 equals 方法时,我们必须确保

  a.equals(a)
  if a.equals(b) then b.equals(a)
  if a.equals(b) and b.equals(c) then a.equals(c)

第一条规则的反例是当所有比较字段 (field1, field2, field3) 都是 null 的情况:

  MyObject a = new MyObject();
  a.setField1(null);
  a.setField2(null);
  a.setField3(null);

  a.equals(a); // <- return false

"

3d规则的反例

"
  MyObject a = new MyObject();
  a.setField1("1"); // field2 and field3 == null 

  MyObject b = new MyObject();
  b.setField1("1"); // field3 == null 
  b.setField2("2"); 

  MyObject c = new MyObject();
  c.setField2("2");  // field1 and field3 == null

  a.equals(b); // true (by field1)
  b.equals(c); // true (by field2)
  a.equals(c); // false!

这就是为什么hashCode()没有解决方案的原因...


你是正确的,但这种情况在实践中不可能发生,因为对象将根据输入数据创建(不幸的是,我不能详细说明用例,我可能会尝试澄清问题,但似乎我应该寻找一个更好的方法)。 - JayK
@JayK:你的实现也违反了3D规则(请看我的编辑)。 - Dmitry Bychenko
你说得对,我还没有考虑到那种情况。如果我要回到这个方法,我必须评估它与实际输入和周围算法的关系,并考虑是否需要适当的“修复”。 - JayK

0
欢迎提供其他解决方案来解决这个问题。
我从不随意更改equals()和hashCode()。只需正确实现它们即可。编写符合您需求的自定义比较器,并使用支持自定义比较器(例如TreeSetTreeMap)的集合来执行查找操作。 示例比较器:
public class SingleFieldMatchComparator implements Comparator<Key> {

  public int compare(Key key1, Key key2) {
    if (key1 == null) {
      if (key2 == null) {
        return 0;
      }
      else {
        return 1;
      }
    } else if (key2 == null) {
      return -1;
    }

    int result = ObjectUtils.compare(key1.getField1(), key2.getField1());
    if (result == 0) {
      return 0;
    }

    result = ObjectUtils.compare(key1.getField2(), key2.getField2());
    if (result == 0) {
      return 0;
    }

    return ObjectUtils.compare(key1.getField3(), key2.getField3());
  }

}

注意:上述代码使用ObjectUtils来减少代码量。如果依赖关系不值得,可以用私有方法替换它。 示例程序:
Map<Key,Key> myMap = new TreeMap<Key,Key>(new SingleFieldMatchComparator());
Key key = new Key(1, 2, 3);
myMap.put(key, key);
key = new Key(3, 1, 2);
myMap.put(key, key);

System.out.println(myMap.get(new Key(null, null, null)));
System.out.println(myMap.get(new Key(1, null, null)));
System.out.println(myMap.get(new Key(null, 2, null)));
System.out.println(myMap.get(new Key(null, null, 2)));
System.out.println(myMap.get(new Key(2, null, null)));

输出:

null
1,2,3
1,2,3
3,1,2
null

0

从你所说的不是一个好的实践开始,我认为你可以通过在每个对象中维护对另一个对象的引用并根据字段的相等性计算hashCode来实现这一点:

public class Test {
    private String field1;
    private Integer field2;

    private Test other;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public int getField2() {
        return field2;
    }

    public void setField2(int field2) {
        this.field2 = field2;
    }

    public Test getOther() {
        return other;
    }

    public void setOther(Test other) {
        this.other = other;
    }

    @Override
    public int hashCode() {
        if (other == null) {
            return super.hashCode();
        }

        int hashCode = 1;
        if (field1 != null && field1.equals(other.field1)) {
            hashCode = 31 * hashCode + (field1 == null ? 0 : field1.hashCode());
        }
        if (field2 != null && field2.equals(other.field2)) {
            hashCode = 31 * hashCode + field2.hashCode();
        }
        if (hashCode == 1) {
            hashCode = super.hashCode();
        }
        return hashCode;
    }

    public boolean equals(Test other) {
        return (field1 != null && field1.equals(other.field1))
                || (field2 != null && field2.equals(other.field2));
    }

    public static void main(String[] args) {
        Test t1 = new Test();
        t1.setField1("a");
        t1.setField2(1);

        Test t2 = new Test();
        t2.setField1("a");
        t2.setField2(1);

        t1.setOther(t2);
        t2.setOther(t1);

        System.out.println("Equals: " + t1.equals(t2));
        System.out.println("Hash 1: " + t1.hashCode());
        System.out.println("Hash 2: " + t2.hashCode());

        t2.setField2(2);
        System.out.println("Equals: " + t1.equals(t2));
        System.out.println("Hash 1: " + t1.hashCode());
        System.out.println("Hash 2: " + t2.hashCode());

        t2.setField1("b");
        System.out.println("Equals: " + t1.equals(t2));
        System.out.println("Hash 1: " + t1.hashCode());
        System.out.println("Hash 2: " + t2.hashCode());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接