HashMap
使用hashCode()
,==
和equals()
用于条目查找。给定键的查找顺序k
如下:
- 用于
k.hashCode()
确定存储条目的存储桶(如果有)
- 如果找到,对于该存储桶中的每个条目的键
k1
,如果k == k1 || k.equals(k1)
,则返回k1
的条目
- 任何其他结果,没有相应的条目
为了演示使用示例,假设我们要创建一个HashMap
where 键是“逻辑上等价”的东西,如果它们具有相同的整数值,由AmbiguousInteger
类表示。然后我们构造一个HashMap
,放入一个条目,然后尝试覆盖它的值并按键检索值。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
}
HashMap<AmbiguousInteger, Integer> map = new HashMap<>();
// logically equivalent keys
AmbiguousInteger key1 = new AmbiguousInteger(1),
key2 = new AmbiguousInteger(1),
key3 = new AmbiguousInteger(1);
map.put(key1, 1); // put in value for entry '1'
map.put(key2, 2); // attempt to override value for entry '1'
System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(key3));
Expected: 2, 2, 2
不要覆盖hashCode()
andequals()
: 默认情况下,Java会hashCode()
为不同的对象生成不同的值,因此HashMap
使用这些值来映射key1
和key2
进入不同的存储桶。key3
没有对应的桶所以没有价值。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Output: 1, 2, null
仅覆盖hashCode()
: 将和HashMap
映射到同一个存储桶中,但由于两者和检查失败,它们仍然是不同的条目,默认情况下使用检查,它们引用不同的实例。两者都失败并检查,因此没有相应的值。key1
key2
key1 == key2
key1.equals(key2)
equals()
==
key3
==
equals()
key1
key2
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Output: 1, 2, null
仅覆盖equals()
: HashMap
将所有键映射到不同的桶中,因为默认不同hashCode()
。==
或equals()
检查在这里无关紧要,因为HashMap
永远不会到达需要使用它们的地步。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Actual: 1, 2, null
覆盖hashCode()
和equals()
:HashMap
映射key1
,key2
和key3
到同一个桶中。==
比较不同实例时检查失败,但equals()
检查通过,因为它们都具有相同的值,并且被我们的逻辑视为“逻辑上等效”。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return value;
}
@Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 2, 2, 2
如果hashCode()
是随机的呢?:HashMap
将为每个操作分配一个不同的存储桶,因此您永远不会找到您之前放入的相同条目。
class AmbiguousInteger {
private static int staticInt;
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return ++staticInt; // every subsequent call gets different value
}
@Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to no bucket, no corresponding value
map.get(key2); // map to no bucket, no corresponding value
map.get(key3); // map to no bucket, no corresponding value
Expected: 2, 2, 2
Actual: null, null, null
如果hashCode()
总是一样呢?:HashMap
将所有键映射到一个大桶中。在这种情况下,您的代码在功能上是正确的,但使用实际上是多余的,因为任何检索都需要在 O(N) 时间(或 Java 8 的 O(logN)HashMap
)内遍历该单个存储桶中的所有条目,等效使用一个.List
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 2, 2, 2
如果equals
总是假的呢?:==
当我们将同一个实例与其自身进行比较时,检查通过,否则失败,equals
检查总是失败key1
,key2
因此key3
被视为“逻辑上不同”,并映射到不同的条目,尽管由于相同的原因它们仍在同一个桶中hashCode()
。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
return false;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Actual: 1, 2, null
好吧,如果equals
现在总是正确的呢?:您基本上是说所有对象都被认为与另一个对象“逻辑上等效”,因此它们都映射到同一个桶(由于相同hashCode()
),相同的条目。
class AmbiguousInteger {
private final int value;
AmbiguousInteger(int value) {
this.value = value;
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.put(new AmbiguousInteger(100), 100); // map to bucket 1, set as entry1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual: 100, 100, 100