可变对象和哈希代码

2022-09-01 20:18:50

具有以下类:

public class Member {
private int x;
private long y;
private double d;

public Member(int x, long y, double d) {
    this.x = x;
    this.y = y;
    this.d = d;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + x;
    result = (int) (prime * result + y);
    result = (int) (prime * result + Double.doubleToLongBits(d));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj instanceof Member) {
        Member other = (Member) obj;
        return other.x == x && other.y == y
                && Double.compare(d, other.d) == 0;
    }
    return false;
}

public static void main(String[] args) {
    Set<Member> test = new HashSet<Member>();
    Member b = new Member(1, 2, 3);
    test.add(b);
    System.out.println(b.hashCode());
    b.x = 0;
    System.out.println(b.hashCode());
    Member first = test.iterator().next();
    System.out.println(test.contains(first));
    System.out.println(b.equals(first));
           System.out.println(test.add(first));

}

}

它会产生以下结果:
30814 29853 false true true

由于哈希代码取决于对象的状态,因此无法再通过正确检索它,因此对包含的检查失败。中的哈希集不再正常工作。解决办法是使成员不可变,但这是唯一的解决办法吗?添加到 HashSet 的所有类都应该是不可变的吗?有没有其他方法可以处理这种情况?

问候。


答案 1

哈希集中的对象应该是不可变的,或者您需要遵守纪律,在哈希集(或哈希映射)中使用后不要更改它们。

在实践中,我很少发现这是一个问题 - 我很少发现自己需要使用复杂的对象作为键或设置元素,当我这样做时,通常不是问题,只是不改变它们。当然,如果此时您已经公开了对其他代码的引用,则可能会变得更加困难。


答案 2

是的。在维护类可变性的同时,您可以根据类的不可变值(可能是生成的id)计算hashCode和equals方法,以遵循Object类中定义的hashCode契约:

  • 每当在执行 Java 应用程序期间在同一对象上多次调用它时,hashCode 方法必须一致地返回相同的整数,前提是不修改对象的相等比较中使用的任何信息。此整数不必从应用程序的一次执行到同一应用程序的另一次执行保持一致。

  • 如果根据 equals(Object) 方法,两个对象相等,则对两个对象中的每个对象调用 hashCode 方法必须生成相同的整数结果。

  • 如果根据 equals(java.lang.Object) 方法,如果两个对象不相等,则不需要在两个对象的每个对象上调用 hashCode 方法必须生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

根据您的情况,这可能更容易或没有。

class Member { 
    private static long id = 0;

    private long id = Member.id++;
    // other members here... 

     
    public int hashCode() { return this.id; }
    public boolean equals( Object o ) { 
        if( this == o ) { return true; }
        if( o instanceOf Member ) { return this.id == ((Member)o).id; }
        return false;
     }
     ...
 }

如果你需要一个线程安全属性,你可以考虑使用:AtomicLong,但同样,这取决于你将如何使用你的对象。