[英]Mutable objects and hashCode
有以下课程:
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
因为hashCode取决于对象的状态,所以不能再正确检索它,因此检查包含失败。 HashSet不再正常工作。 解决方案是使成员不可变,但这是唯一的解决方案吗? 是否所有添加到HashSet的类都是不可变的? 有没有其他方法来处理这种情况?
问候。
在hashsets对象要么是不可变的, 或者你需要在他们已经在一个HashSet(或HashMap的),使用后不改变他们行使纪律。
在实践中,我很少发现这是一个问题 - 我很少发现自己需要使用复杂的对象作为键或设置元素,而当我这样做时,通常不是一个问题,只是不要改变它们。 当然,如果你此时已经公开了对其他代码的引用,那么它会变得更难。
是。 在维护类可变的同时,可以根据类的不可变值(可能是生成的id)计算hashCode和equals方法,以遵守Object类中定义的hashCode契约:
每当在执行Java应用程序期间多次在同一对象上调用它时,hashCode方法必须始终返回相同的整数,前提是不修改对象的equals比较中使用的信息。 从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
如果两个对象根据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 ,但同样,它取决于你将如何使用你的对象。
如前所述,人们可以接受以下三种解决方案:
hashcode
实现上使用不可变标识并equals
检查,例如类似ID的值。 add
/ remove
以获取插入对象的克隆,而不是实际引用。 HashSet
不提供get
函数(例如,允许您稍后更改对象); 因此,你是安全的,不存在重复。 但是,如果出于某种原因,您确实需要在插入HashSet
后修改对象,则需要找到一种方法,通过新的更改“通知”您的Collection。 要实现此功能:
HashSet
以实现Observer
接口。 您的Member
对象必须是Observable
并在任何影响hashcode
和/或equals
setter或其他方法上update
HashSet
。 注1:扩展3,使用4:我们可以接受更改,但是那些不创建已存在对象的更改(例如,我通过分配新ID而不是将其设置为现有ID来更新用户ID)。 否则,您必须考虑以这样的方式转换对象的方案,该方式现在等于Set
已存在的另一个对象。 如果您接受此限制,第4条建议将正常工作,否则您必须主动并为此类案例定义政策。
注意2:您必须在update
实现上提供更改对象的先前和当前状态,因为您必须首先删除旧元素(例如,在设置新值之前使用getClone()
),然后添加具有新状态的对象。 以下代码段只是一个示例实现,它需要根据您添加重复项的策略进行更改。
@Override
public void update(Observable newItem, Object oldItem) {
remove(oldItem);
if (add(newItem))
newItem.addObserver(this);
}
我在项目中使用了类似的技术,我需要在一个类上使用多个索引,所以我可以使用O(1)查找共享一个共同标识的对象集; 想象它作为HashSets的MultiKeymap(这非常有用,因为你可以交叉/联合索引并且类似于类似SQL的搜索工作)。 在这种情况下,我注释必须fireChange的方法(通常是setter) - 在发生重大更改时更新每个索引,因此索引始终使用最新状态进行更新。
Jon Skeet列出了所有替代品。 至于为什么Map或Set中的键不能改变:
集合的契约意味着在任何时候,没有两个对象o1和o2这样
o1 != o2 && set.contains(o1) && set.contains(o2) && o1.equals(o2)
为什么需要这一点对于Map来说尤为明显。 从Map.get()的合同:
更正式地说,如果此映射包含从键
k
到值v
的映射,使得(key==null ? k==null : key.equals(k))
,则此方法返回v
,否则返回null
。 (最多可以有一个这样的映射。)
现在,如果修改插入到地图中的键,则可能使其等于已插入的其他键。 而且,地图不能知道你已经这样做了。 那么,如果你做map.get(key)
,那么地图应该做什么,其中key
等于地图中的几个键? 没有直观的方法来定义这意味着什么 - 主要是因为我们对这些数据类型的直觉是集合和映射的数学理想,它们不必处理更改键,因为它们的键是数学对象,因此是不可变的。
理论上(通常也是实际上也是如此)你的课程:
hashCode
。 Set
来存储它们是不必要的,你也可以使用List
。 放入基于散列的容器后,切勿更改'hashable field'。
好像您(会员)在黄页(基于散列的容器)中注册了您的电话号码(Member.x),但您更改了号码,那么没有人能再在黄页中找到您。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.