HashMap
hashCode()
、==
およびequals()
エントリの検索に使用します。特定のキーのルックアップ シーケンスk
は次のとおりです。
- エントリが格納されているバケットを特定するために使用
k.hashCode()
します (存在する場合)。
k1
見つかった場合、そのバケット内の各エントリのキーについて、 の場合はのエントリk == k1 || k.equals(k1)
を返しますk1
- その他の結果、対応するエントリなし
例を使用して説明するために、クラスHashMap
で表される同じ整数値を持つ場合、キーが「論理的に同等」である場所を作成するとします。AmbiguousInteger
次に、 を作成しHashMap
、1 つのエントリに入れ、その値をオーバーライドして、キーで値を取得しようとします。
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()
and をオーバーライドしないでくださいequals()
。デフォルトでは、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
マップしますが、デフォルトではチェックを使用し、異なるインスタンスを参照するため、とチェックの両方が失敗するため、それらは異なるエントリのままです。は両方とも失敗し、andに対してチェックするため、対応する値はありません。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
mapskey1
の両方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
すべてのキーを 1 つの大きなバケットにマップします。この場合、コードは機能的には正しいですが、 の使用HashMap
は実質的に冗長です。これは、取得が O(N) 時間 ( Java 8 の場合は O(logN) )でその単一のバケット内のすべてのエントリを反復処理する必要があるためです。の使用に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
が常に false の場合はどうなるでしょうか。:==
同じインスタンスをそれ自体と比較するとチェックに合格しますが、それ以外の場合は失敗します。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
が常に true の場合はどうなりますか? : 基本的に、すべてのオブジェクトが別のオブジェクトと「論理的に同等」であると見なされると言っているため、それらはすべて同じバケット (同じため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