"equals any of some fields are equal"的哈希代码实现



如果一个的字段相等,我希望特定类的对象相等。如何为这样的类编写一致的hashCode方法?

(免责声明,因为我知道这不是最佳实践:该类是另一个类的包装器,应该用于Maps中的键。这是为了让两个不同的对象有一个相等的字段,从而产生相同的Map条目。实际上,每个字段都会自己识别底层对象,但我并不总是为两个可用对象提供相同的识别字段无法控制并因此改变这种"模棱两可"的识别机制。解决这一问题的替代解决方案也很受欢迎。)

是否有相应的策略来实现hashCode()?我只知道一些实现涉及等号中的连词(如与&&)。如果任意一个字段相等,如何确保hashCode()是相同的?

下面是一个简化的equals方法,我想为它编写一个一致的hashCode()实现:

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)的情况。对象的生成只是为了使某些字段可以为null,而不是像示例中那样矛盾。简单地说,在实践中,等于(1,2,3)的唯一组合应该是(1,2,3)、(1,null,null)、(null,2,null)和(1,2,null)等等。我承认,这种做法不是特别有力。

不能使用任何字段来实现hashCode,因为字段并不总是相等的。

hashCode方法需要始终为equals对象返回相同的值。由于在equals方法中只有一个字段需要相等,而且它并不总是相同的,因此您唯一的选择是在hashCode方法中返回一个常量。

该实施效率低下,但它是有效的,并且与平等者保持一致。

实施方式可以是:

public int hashCode() {
    return 0;
}

通常不使用对象类的一个字段来实现equals()hashCode()。每个人可能都会建议你不要这样做。通常的做法是确保你比较所有字段,并确保它们都相等,以便调用.equals()hashCode()使用.equals()来散列那些对象。但是,如果您可以控制您正在做的事情,您可以简单地使用对象的特定字段的hashCode(),并基于此重写.equals()和.hashCode()(但同样,这是不可取的)。

似乎唯一的解决方案是这个

public int hashCode() {
    return 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));
  }

错误的。在实施平等时,我们必须确保

  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()没有的解决方案。。。

从你所说的这不是一个好的做法开始,我认为实现这一点的一种方法是在每个对象中保留对另一个对象的引用,并基于字段的相等性计算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());
    }
}

解决这一问题的替代解决方案也受到欢迎。

我永远不会弄乱equals()&hashCode()。只需为对象正确地实现它们。编写一个满足您需求的自定义比较器,并使用支持自定义比较器的集合(例如TreeSet或TreeMap)来执行查找。

样品比较器:

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

相关内容

  • 没有找到相关文章