简体   繁体   English

可变对象和hashCode

[英]Mutable objects and hashCode

Have the following class: 有以下课程:

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));

}

} }

It produces the following results: 它产生以下结果:
30814 29853 false true true

Because the hashCode depends of the state of the object it can no longer by retrieved properly, so the check for containment fails. 因为hashCode取决于对象的状态,所以不能再正确检索它,因此检查包含失败。 The HashSet in no longer working properly. HashSet不再正常工作。 A solution would be to make Member immutable, but is that the only solution? 解决方案是使成员不可变,但这是唯一的解决方案吗? Should all classes added to HashSets be immutable? 是否所有添加到HashSet的类都是不可变的? Is there any other way to handle the situation? 有没有其他方法来处理这种情况?

Regards. 问候。

Objects in hashsets should either be immutable, or you need to exercise discipline in not changing them after they've been used in a hashset (or hashmap). 在hashsets对象要么是不可变的, 或者你需要在他们已经在一个HashSet(或HashMap的),使用后不改变他们行使纪律。

In practice I've rarely found this to be a problem - I rarely find myself needing to use complex objects as keys or set elements, and when I do it's usually not a problem just not to mutate them. 在实践中,我很少发现这是一个问题 - 我很少发现自己需要使用复杂的对象作为键或设置元素,而当我这样做时,通常不是一个问题,只是不要改变它们。 Of course if you've exposed the references to other code by this time, it can become harder. 当然,如果你此时已经公开了对其他代码的引用,那么它会变得更难。

Yes. 是。 While maintaining your class mutable, you can compute the hashCode and the equals methods based on immutable values of the class ( perhaps a generated id ) to adhere to the hashCode contract defined in Object class: 在维护类可变的同时,可以根据类的不可变值(可能是生成的id)计算hashCode和equals方法,以遵守Object类中定义的hashCode契约:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. 每当在执行Java应用程序期间多次在同一对象上调用它时,hashCode方法必须始终返回相同的整数,前提是不修改对象的equals比较中使用的信息。 This integer need not remain consistent from one execution of an application to another execution of the same application. 从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. 如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. 如果两个对象根据equals(java.lang.Object)方法不相等,则不需要在两个对象中的每一个上调用hashCode方法必须生成不同的整数结果。 However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables. 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。

Depending on your situation this may be easier or not. 根据您的情况,这可能更容易或不容易。

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;
     }
     ...
 }

If you need a thread safe attribute, you may consider use: AtomicLong instead, but again, it depends on how are you going to use your object. 如果你需要一个线程安全属性,你可以考虑使用: AtomicLong ,但同样,它取决于你将如何使用你的对象。

As already mentioned, one can accept the following three solutions: 如前所述,人们可以接受以下三种解决方案:

  1. Use immutable objects; 使用不可变对象; even when your class is mutable, you may use immutable identities on your hashcode implementation and equals checking, eg an ID-like value. 即使您的类是可变的,您也可以在hashcode实现上使用不可变标识并equals检查,例如类似ID的值。
  2. Similarly to the above, implement add / remove to get a clone of the inserted object, not the actual reference. 与上面类似,实现add / remove以获取插入对象的克隆,而不是实际引用。 HashSet does not offer a get function (eg to allow you alter the object later on); HashSet不提供get函数(例如,允许您稍后更改对象); thus, you are safe there won't exist duplicates. 因此,你是安全的,不存在重复。
  3. Exercise discipline in not changing them after they've been used, as @ Jon Skeet suggests 正如@ Jon Skeet建议的那样,在使用它们之后不要改变它们的运动纪律

But, if for some reason you really need to modify objects after being inserted to a HashSet , you need to find a way of "informing" your Collection with the new changes. 但是,如果出于某种原因,您确实需要在插入HashSet后修改对象,则需要找到一种方法,通过新的更改“通知”您的Collection。 To achieve this functionality: 要实现此功能:

  1. You can use the Observer design pattern, and extend HashSet to implement the Observer interface. 您可以使用Observer设计模式,并扩展HashSet以实现Observer接口。 Your Member objects must be Observable and update the HashSet on any setter or other method that affects hashcode and/or equals . 您的Member对象必须是Observable并在任何影响hashcode和/或equals setter或其他方法上update HashSet

Note 1: Extending 3, using 4: we may accept alterations, but those that do not create an already existing object (eg I updated a user's ID, by assigning a new ID, not setting it to an existing one). 注1:扩展3,使用4:我们可以接受更改,但是那些不创建已存在对象的更改(例如,我通过分配新ID而不是将其设置为现有ID来更新用户ID)。 Otherwise, you have to consider the scenario where an object is transformed in such a way that is now equal to another object already existing in the Set . 否则,您必须考虑以这样的方式转换对象的方案,该方式现在等于Set已存在的另一个对象。 If you accept this limitation, 4th suggestion will work fine, else you must be proactive and define a policy for such cases. 如果您接受此限制,第4条建议将正常工作,否则您必须主动并为此类案例定义政策。

Note 2: You have to provide both previous and current states of the altered object on your update implementation, because you have to initially remove the older element (eg use getClone() before setting new values), then add the object with the new state. 注意2:您必须在update实现上提供更改对象的先前和当前状态,因为您必须首先删除旧元素(例如,在设置新值之前使用getClone() ),然后添加具有新状态的对象。 The following snippet is just an example implementation, it needs changes based on your policy of adding a duplicate. 以下代码段只是一个示例实现,它需要根据您添加重复项的策略进行更改。

@Override
public void update(Observable newItem, Object oldItem) {
    remove(oldItem);
    if (add(newItem))
        newItem.addObserver(this);
}

I've used similar techniques on projects, where I require multiple indices on a class, so I can look up with O(1) for Sets of objects that share a common identity; 我在项目中使用了类似的技术,我需要在一个类上使用多个索引,所以我可以使用O(1)查找共享一个共同标识的对象集; imagine it as a MultiKeymap of HashSets (this is really useful, as you can then intersect/union indices and work similarly to SQL-like searching). 想象它作为HashSets的MultiKeymap(这非常有用,因为你可以交叉/联合索引并且类似于类似SQL的搜索工作)。 In such cases I annotate methods (usually setters) that must fireChange-update each of the indices when a significant change occurs, so indices are always updated with the latest states. 在这种情况下,我注释必须fireChange的方法(通常是setter) - 在发生重大更改时更新每个索引,因此索引始终使用最新状态进行更新。

Jon Skeet has listed all alternatives. Jon Skeet列出了所有替代品。 As for why the keys in a Map or Set must not change: 至于为什么Map或Set中的键不能改变:

The contract of a Set implies that at any time, there are no two objects o1 and o2 such that 集合的契约意味着在任何时候,没有两个对象o1和o2这样

o1 != o2 && set.contains(o1) && set.contains(o2) && o1.equals(o2)

Why that is required is especially clear for a Map. 为什么需要这一点对于Map来说尤为明显。 From the contract of Map.get(): 从Map.get()的合同:

More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)) , then this method returns v , otherwise it returns null . 更正式地说,如果此映射包含从键k到值v的映射,使得(key==null ? k==null : key.equals(k)) ,则此方法返回v ,否则返回null (There can be at most one such mapping.) (最多可以有一个这样的映射。)

Now, if you modify a key inserted into a map, you might make it equal to some other key already inserted. 现在,如果修改插入到地图中的键,则可能使其等于已插入的其他键。 Moreover, the map can not know that you have done so. 而且,地图不能知道你已经这样做了。 So what should the map do if you then do map.get(key) , where key is equal to several keys in the map? 那么,如果你做map.get(key) ,那么地图应该做什么,其中key等于地图中的几个键? There is no intuitive way to define what that would mean - chiefly because our intuition for these datatypes is the mathematical ideal of sets and mappings, which don't have to deal with changing keys, since their keys are mathematical objects and hence immutable. 没有直观的方法来定义这意味着什么 - 主要是因为我们对这些数据类型的直觉是集合和映射的数学理想,它们不必处理更改键,因为它们的键是数学对象,因此是不可变的。

Theoretically (and more often than not practically too) your class either: 理论上(通常也是实际上也是如此)你的课程:

  1. has a natural immutable identity that can be inferred from a subset of its fields, in which case you can use those fields to generate the hashCode from. 具有自然不可变的标识,可以从其字段的子集推断出来,在这种情况下,您可以使用这些字段从中生成hashCode
  2. has no natural identity, in which case using a Set to store them is unnecessary, you could just as well use a List . 没有自然的身份,在这种情况下使用Set来存储它们是不必要的,你也可以使用List

Never change 'hashable field" after putting in hash based container. 放入基于散列的容器后,切勿更改'hashable field'。

As if you (Member) registered your phone number (Member.x) in yellow page(hash based container), but you changed your number, then no one can find you in the yellow page any more. 好像您(会员)在黄页(基于散列的容器)中注册了您的电话号码(Member.x),但您更改了号码,那么没有人能再在黄页中找到您。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM