本文中我们将会讨论在Java HashMap中将可变对象用作Key。所有的Java程序员可能都在自己的编程经历中多次用过HashMap。那什么是HashMap呢?
HashMap是一种用哈希值来存储和查找键值对(key-value pair,也称作entry)的一种数据结构。
为了正确使用HashMap,选择恰当的Key是非常重要的。Key在HashMap里是不可重复的。
可变对象是指创建后自身状态能改变的对象。换句话说,可变对象是该对象在创建后它的哈希值可能被改变。
在下面的代码中,对象MutableKey的键在创建时变量 i=10 j=20,哈希值是1291。
然后我们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。现在Key的哈希值已经变成1931。
显然,这个对象的键在创建后发生了改变。所以类MutableKey是可变的。
让我们看看下面的示例代码:
注意:调用hashCode()时,equals()方法也同时被执行。
publicclass MutableKey { privateint i; privateint j; publicMutableKey(inti, intj) { this.i = i; this.j = j; } publicfinal int getI() { returni; } publicfinal void setI(inti) { this.i = i; } publicfinal int getJ() { returnj; } publicfinal void setJ(intj) { this.j = j; } @Override publicint hashCode() { finalint prime = 31; intresult = 1; result = prime * result + i; result = prime * result + j; returnresult; } @Override publicboolean equals(Object obj) { if(this== obj) { returntrue; } if(obj == null) { returnfalse; } if(!(obj instanceofMutableKey)) { returnfalse; } MutableKey other = (MutableKey) obj; if(i != other.i) { returnfalse; } if(j != other.j) { returnfalse; } returntrue; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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());
}
}
|
输出:
1
2
|
Hash code: 1291
Hash code: 1931
|
HashMap用Key的哈希值来存储和查找键值对。
当插入一个Entry时,HashMap会计算Entry Key的哈希值。Map会根据这个哈希值把Entry插入到相应的位置。
查找时,HashMap通过计算Key的哈希值到特定位置查找这个Entry。
如果HashMap Key的哈希值在存储键值对后发生改变,Map可能再也查找不到这个Entry了。
如果Key对象是可变的,那么Key的哈希值就可能改变。在HashMap中可变对象作为Key会造成数据丢失。
下面的例子将会向你展示HashMap中有可变对象作为Key带来的问题。
importjava.util.HashMap; importjava.util.Map; publicclassMutableDemo1 { publicstaticvoidmain(String[] args) { // HashMap Map<MutableKey, String> map = newHashMap<>(); // Object created MutableKey key = newMutableKey(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)); } }
输出:
1
2
|
Robin
null
|
在HashMap中使用不可变对象。在HashMap中,使用String、Integer等不可变类型用作Key是非常明智的。
我们也能定义属于自己的不可变类。
如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。
在下面的Employee示例类中,哈希值是用实例变量id来计算的。一旦Employee的对象被创建,id的值就不能再改变。只有name可以改变,但name不能用来计算哈希值。所以,一旦Employee对象被创建,它的哈希值不会改变。所以Employee在HashMap中用作Key是安全的。
示例代码:
importjava.util.HashMap; importjava.util.Map; publicclassMutableSafeKeyDemo { publicstaticvoid main(String[] args) { Employee emp = newEmployee(2); emp.setName("Robin"); // Put object in HashMap. Map<Employee, String> map = newHashMap<>(); 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)); } } classEmployee { // It is specified while object creation. // Cannot be changed once object is created. No setter for this field. privateintid; privateString name; publicEmployee(finalintid) { this.id = id; } publicfinalString getName() { returnname; } publicfinalvoid setName(finalString name) { this.name = name; } publicintgetId() { returnid; } // 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 publicinthashCode() { finalintprime = 31; intresult = 1; result = prime * result + id; returnresult; } @Override publicbooleanequals(Object obj) { if(this== obj) returntrue; if(obj == null) returnfalse; if(getClass() != obj.getClass()) returnfalse; Employee other = (Employee) obj; if(id != other.id) returnfalse; returntrue; } }
输出:
1
2
|
Showbasky
Showbasky
|
现在你可能已经理解在HashMap中用可变对象作为Key的危险了。关于本文欢迎提出的任何建议和意见。