如果 hashCode()
计算使用不可变字段并且equals()
使用所有字段,那么当该类用作哈希值时会出现问题键? E.g。
import java.util.Objects;
public class Car {
protected final long vin;
protected String state;
protected String plateNumber;
public Car( long v, String s, String p ) {
vin = v; state = s; plateNumber = p;
}
public void move( String s, String p ) {
state = s; plateNumber = p;
}
public int hashCode() {
return (int)( vin % Integer.MAX_VALUE );
}
public boolean equals( Object other ) {
if (this == other) return true;
else if (!(other instanceof Car)) return false;
Car otherCar = (Car) other;
return vin == otherCar.vin
&& Objects.equals( state, otherCar.state )
&& Objects.equals( plateNumber, otherCar.plateNumber );
}
}
在car对象插入到hashset之后调用move(),可以通过保存在别处的引用来实现。
我不是在追问性能问题。只有正确性。
我已经阅读了java hashCode contact,很少有答案,包括this由尊敬的Jon Skeet和this来自大蓝。我觉得最后一个链接给出了最好的解释,并暗示上面的代码是正确的。
修改
结论:
该类满足java中'equals()'和'hashCode()'的约束。但是,当用作集合中的键,散列或不散列时,它违反了对'equals()'的额外要求的限制
附加要求是,只要对象是键,'equals()'就必须保持一致
请参阅路易斯·沃瑟曼的反例和下面道格拉斯提供的参考资料。
很少澄清:
A)此类满足java对象级别约束:
请注意,“1”和“2”的反向不是必需的。上述课程满足所有条件 此外,java文档提到“equals()...实现对象上最具辨别力的可能等价关系”,但不确定这是否是强制性的。
B)就性能而言,碰撞避免概率的增量随着我们组合的每个连续成员变量而减小。通常很少有精心挑选的成员变量就足够了。
答案 0 :(得分:2)
仅考虑hashCode
和equals
合同时,您认为此实施符合其要求是正确的。 hashCode
使用equals
使用的严格字段子集足以保证a.equals(b)
根据需要隐含a.hashCode() == b.hashCode()
。
但是,当您引入Map
时,情况会发生变化。从Map
javadoc,&#34;如果对象的值以影响等于比较的方式更改,而对象是地图中的键,则不指定地图的行为。&#34; < / p>
在move
Car
上调用Map
之后,Map
的行为现在未指定。Map
的行为现在未指定。在许多情况下,它在实践中仍将以您希望的方式工作,但奇怪的事情可能以难以预测的方式发生。虽然从技术上讲
Car car1 = ...
Car car2 = ... // a copy of car1
Map<Car, String> map1 = ...
map1.put(car1, "value");
assert map1.get(car2).equals("value"); // true
car1.move(...);
assert map1.get(car2).equals("value"); // NullPointerException on the equals call, car2 is no longer found
自发清空或切换所有查找以使用随机数生成器在技术上是有效的,但更可能的情况可能是这样:
car2
请注意,Map
和car2
都没有以任何方式改变自己,但Map
的映射无论如何都改变了(或者更确切地说,消失了)。这种行为没有正式指定,但我猜大多数dplyr
实现都会这样做。
答案 1 :(得分:1)
如果您从未在move
位于地图后致电Car
,那是正确的。否则就错了。密钥在地图中后,hashCode
和 equals
都必须保持一致。
答案 2 :(得分:1)
您可以根据需要改变您的关键候选人,在他们被用作键之前或之后(而不是在期间)。 实际上,执行这条规则非常困难。如果您改变了对象,那么如果有人将它们用作关键字,则您没有控件。
密钥的不变性更容易,消除了微妙的,难以发现的错误的来源,只是更好地为密钥工作。
在您的情况下,我发现没有正确性问题。但是为什么你不打算在哈希码中包含所有字段?
答案 3 :(得分:1)
哈希通过将项目放入“桶”来工作。每个桶都由hashcode
计算。找到存储桶后,搜索会继续使用equals
逐个比较每个项目。
例如:
总结:哈希码和实际键一起工作。前者用于知道项目应该在哪个桶中。后者由equals
比较使用,寻找实际项目返回。
答案 4 :(得分:1)
Short answer: it should be OK, but prepare for bizarre behavior.
Longer answer: when you change fields that participate in equals() on a key, the value keyed by that key will no longer be found.
Still longer answer: this looks as X/Y problem: you're asking about X, but you really need X to accomplish Y. Maybe you should ask about Y?
The car in your case is uniquely identified by body {
font-family: "Lato", sans-serif;
padding-top: 30px;
}
.toggleNav {
position: fixed;
left: 5px;
top: 5px;
font-size: 30px;
cursor: pointer;
z-index: 10;
opacity: 1;
transition: transform .5s ease, color .5s, opacity .5s;
}
.nav-is-open .toggleNav {
transform: translateX( calc( 50vw - 40px ) );
color: #fff;
opacity: .5;
}
.nav-is-open .toggleNav::before {
content: '\000D7';
}
.nav-is-open .toggleNav > span {
display: none;
}
.nav-is-open .toggleNav:hover {
opacity: 1;
}
/* Combine same styles of both sides in one class */
.Sidenav {
position: fixed;
top: 0;
height: 100%;
width: 0;
z-index: 1;
padding-top: 60px;
background-color: #111;
overflow-x: hidden;
transition: width .5s ease;
}
.leftSidenav {
left: 0;
}
.rightSidenav {
right: 0;
/* Instead of having width: 0 we transform it to the side
This prevents text from wrapping */
width: 50%;
transform: translateX( 100% );
transition: transform .5s ease;
}
/* Combine link styles into one */
.Sidenav a {
display: block;
padding: 8px 8px 8px 32px;
text-decoration: none;
font-size: 25px;
color: #818181;
transition: 0.3s;
}
.Sidenav a:hover {
color: #f1f1f1;
}
.nav-is-open .Sidenav {
width: 50%;
transform: translateX( 0 );
}
@media screen and (max-height: 450px) {
.Sidenav {
padding-top: 15px;
}
.Sidenav a {
font-size: 18px;
}
}
. A car equals to itself. But, a car can be registered in different states. Maybe the answer is to have a Registration object (or a few of them) attached to the car? And then you can separate <div class="Sidenav leftSidenav">
<a href="#">About</a>
<a href="#">Services</a>
<a href="#">Clients</a>
<a href="#">Contact</a>
</div>
<div class="Sidenav rightSidenav">
<a href="#">About 2</a>
<a href="#">Services 2</a>
<a href="#">Clients 2</a>
<a href="#">Contact 2</a>
</div>
<a class="toggleNav" onclick="toggleNav()"><span>☰</span></a>
from .nav-is-open
.
答案 5 :(得分:0)
当您的hashCode()
实施仅使用有限数量的字段(vs equals
)时,您几乎会降低使用散列的所有算法/数据结构的效果:HashMap
,{ {1}}等等。您正在增加碰撞概率 - 当两个不同的对象(HashSet
返回false)具有相同的哈希值时,情况就会出现。
答案 6 :(得分:0)
简短的回答是:否。
答案很长:
完全不变性不是必需的。但是:
等于必须仅依赖于不可变值。 Hashcode必须依赖于不可变值,或者是equals中使用的值的常量或子集,或者是equals中使用的所有值。 equals中未提及的值不得是哈希码的一部分。
如果你改变值等于和hashcode依赖,很可能你没有在基于散列的数据结构中再次找到你的对象。看看这个:
public class Test {
private static class TestObject {
private String s;
public TestObject(String s) {
super();
this.s = s;
}
public void setS(String s) {
this.s = s;
}
@Override
public boolean equals(Object obj) {
boolean equals = false;
if (obj instanceof TestObject) {
TestObject that = (TestObject) obj;
equals = this.s.equals(that.s);
}
return equals;
}
@Override
public int hashCode() {
return this.s.hashCode();
}
}
public static void main(String[] args) {
TestObject a1 = new TestObject("A");
TestObject a2 = new TestObject("A");
System.out.println(a1.equals(a2)); // true
HashMap<TestObject, Object> hashSet = new HashMap<>(); // hash based datastructure
hashSet.put(a1, new Object());
System.out.println(hashSet.containsKey(a1)); // true
a1.setS("A*");
System.out.println(hashSet.containsKey(a1)); // false !!! // Object is not found as the hashcode used initially before mutation was used to determine the hash bucket
a2.setS("A*");
System.out.println(hashSet.containsKey(a2)); // false !!! Because a1 is in wrong hash bucket ...
System.out.println(a1.equals(a2)); // ... even if the objects are equals
}
}