Java 在 Map 中使用複雜資料型別作為 Key
HashMap是一種用雜湊值來儲存和查詢鍵值對(key-value pair,也稱作entry)的一種資料結構。
為了正確使用HashMap,選擇恰當的Key是非常重要的。Key在HashMap裡是不可重複的。
1、什麼是可變物件
可變物件是指建立後自身狀態能改變的物件。換句話說,可變物件是該物件在建立後它的雜湊值可能被改變。
在下面的程式碼中,物件MutableKey的鍵在建立時變數 i=10 j=20,雜湊值是1291。
然後我們改變例項的變數值,該物件的鍵 i 和 j 從10和20分別改變成30和40。現在Key的雜湊值已經變成1931。
顯然,這個物件的鍵在建立後發生了改變。所以類MutableKey是可變的。
讓我們看看下面的示例程式碼:
注意:呼叫hashCode()時,equals()方法也同時被執行。
public class MutableKey { private int i; private int j; public MutableKey(int i, int j) { this.i = i; this.j = j; } public final int getI() { return i; } public final void setI(int i) { this.i = i; } public final int getJ() { return j; } public final void setJ(int j) { this.j = j; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + i; result = prime * result + j; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof MutableKey)) { return false; } MutableKey other = (MutableKey) obj; if (i != other.i) { return false; } if (j != other.j) { return false; } return true; } } public class MutableDemo { public static void main(String[] args) { // Object created MutableKey key = new MutableKey(10, 20); System.out.println("Hash code: " + key.hashCode()); // Object State is changed after object creation. key.setI(30); key.setJ(40); System.out.println("Hash code: " + key.hashCode()); } }
2、HashMap如何儲存鍵值對
HashMap用Key的雜湊值來儲存和查詢鍵值對。
當插入一個Entry時,HashMap會計算Entry Key的雜湊值。Map會根據這個雜湊值把Entry插入到相應的位置。
查詢時,HashMap通過計算Key的雜湊值到特定位置查詢這個Entry。
3、在HashMap中使用可變物件作為Key帶來的問題
如果HashMap Key的雜湊值在儲存鍵值對後發生改變,Map可能再也查詢不到這個Entry了。
如果Key物件是可變的,那麼Key的雜湊值就可能改變。在HashMap中可變物件作為Key會造成資料丟失。
下面的例子將會向你展示HashMap中有可變物件作為Key帶來的問題
import java.util.HashMap;
import java.util.Map;
public class MutableDemo1 {
public static void main(String[] args) {
// HashMap
Map<MutableKey, String> map = new HashMap<>();
// Object created
MutableKey key = new MutableKey(10, 20);
// Insert entry.
map.put(key, "Robin");
// This line will print 'Robin'
System.out.println(map.get(key));
// Object State is changed after object creation.
// i.e. Object hash code will be changed.
key.setI(30);
// This line will print null as Map would be unable to retrieve the
// entry.
System.out.println(map.get(key));
}
}
Robin
null
4、如何解決
在HashMap中使用不可變物件。在HashMap中,使用String、Integer等不可變型別用作Key是非常明智的。
我們也能定義屬於自己的不可變類。
如果可變物件在HashMap中被用作鍵,那就要小心在改變物件狀態的時候,不要改變它的雜湊值了。
在下面的Employee示例類中,雜湊值是用例項變數id來計算的。一旦Employee的物件被建立,id的值就能再改變。只有name可以改變,但name不能用來計算雜湊值。所以,一旦Employee物件被建立,它的雜湊值不會改變。所以Employee在HashMap中用作Key是安全的。
import java.util.HashMap;
import java.util.Map;
public class MutableSafeKeyDemo {
public static void main(String[] args) {
Employee emp = new Employee(2);
emp.setName("Robin");
// Put object in HashMap.
Map<Employee, String> map = new HashMap<>();
map.put(emp, "Showbasky");
System.out.println(map.get(emp));
// Change Employee name. Change in 'name' has no effect
// on hash code.
emp.setName("Lily");
System.out.println(map.get(emp));
}
}
class Employee {
// It is specified while object creation.
// Cannot be changed once object is created. No setter for this field.
private int id;
private String name;
public Employee(final int id) {
this.id = id;
}
public final String getName() {
return name;
}
public final void setName(final String name) {
this.name = name;
}
public int getId() {
return id;
}
// Hash code depends only on 'id' which cannot be
// changed once object is created. So hash code will not change
// on object's state change
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (id != other.id)
return false;
return true;
}
}
12 | Showbasky Showbasky |
總結:眾所周知,map的key以不變的型別,比如String,Integer,作為key是最明智的,當然如果要以可變物件作為key的話,那就必須要重寫hashcode和equals方法來達到這個目的,除此之外,別無他法。