存储现有值时,Java的HashMap密钥替换
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了存储现有值时,Java的HashMap密钥替换相关的知识,希望对你有一定的参考价值。
根据Java HashMap文档,put
方法替换了以前包含的值(如果有的话):https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#put-K-V-
将指定的值与此映射中的指定键相关联。如果映射先前包含键的映射,则替换旧值。
然而,文档没有说明存储新值时(现有)密钥会发生什么。现有密钥是否被替换?或结果未定义?
请考虑以下示例:
public class HashMapTest
{
private static class Key {
private String value;
private Boolean b;
private Key(String value, Boolean b) {
this.value = value;
this.b = b;
}
@Override
public int hashCode()
{
return value.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Key)
{
return value.equals(((Key)obj).value);
}
return false;
}
@Override
public String toString()
{
return "(" + value.toString() + "-" + b + ")";
}
}
public static void main(String[] arg) {
Key key1 = new Key("foo", true);
Key key2 = new Key("foo", false);
HashMap<Key, Object> map = new HashMap<Key, Object>();
map.put(key1, 1L);
System.out.println("Print content of original map:");
for (Entry<Key, Object> entry : map.entrySet()) {
System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
}
map.put(key2, 2L);
System.out.println();
System.out.println("Print content of updated map:");
for (Entry<Key, Object> entry : map.entrySet()) {
System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
}
}
}
当我使用Oracle jdk1.8.0_121执行以下代码时,会生成以下输出:
Print content of original map:
> (foo-true) -> 1
Print content of updated map:
> (foo-true) -> 2
证据表明(至少在我的电脑上)现有密钥不会被替换。
这是预期/定义的行为(它在何处定义?)或者它只是所有可能结果中的一个?我可以指望这种行为在所有Java平台/版本中保持一致吗?
编辑:这个问题不是What happens when a duplicate key is put into a HashMap?的重复。我问的是密钥(即当你使用多个引用相同逻辑密钥的密钥实例时),而不是关于值。
从查看source,它没有被取代,我不确定它是否由合同保证。
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
它找到了现有的映射并替换了值,没有用新密钥完成,它们应该是相同且不可变的,所以即使不同的实现可以替换密钥也无关紧要。
你不能指望这种行为,但你应该以一种无关紧要的方式编写你的代码。
添加新对时,地图使用hasCode
,equals
来检查地图中是否已存在该关键字。如果密钥已存在,则旧值将替换为新值。钥匙本身仍未修改。
Map<Integer,String> map = new HashMap<>();
map.put(1,"two");
System.out.println(map); // {1=two}
map.put(1,"one");
System.out.println(map); // {1=one}
map.put(2,"two");
System.out.println(map); // {1=one, 2=two}
您的equals
和hashCode
合同存在问题。根据您的实施情况,ke1
和key2
完全相同:
@Override
public boolean equals(Object obj)
{
if (obj instanceof Key)
{
return value.equals(((Key)obj).value);
}
return false;
}
你还需要比较Boolean b
Key other = (Key) obj;
return value.equals(other.value) && b.equals(other.b);
同样的规则适用于hasCode
@Override
public int hashCode()
{
return value.hashCode();
}
return value.hashCode() + b.hashCode();
随着这些变化key1
和key2
是不同的
System.out.println(key1.equals(key2));
并且地图的输出将是
> (foo-true) -> 1
> (foo-false) -> 2
它没有被替换 - 它也不应该被替换。如果你知道HashMap
如何工作以及hashCode
和equals
是什么(或更确切地说是如何使用它们) - 不接触Key
的决定是显而易见的。
当你把另一个Key / Entry第二次放在地图中时,那个键首先在地图中查找 - 根据hashCode/equals
,所以根据map
,IFF键具有相同的hashCode并且根据equals
相等它们是相同。如果是这样,为什么要更换它?特别是因为如果它已经被替换,那么可能需要操作额外的操作或至少额外的代码,以便在键相等时不触发任何其他操作。
以上是关于存储现有值时,Java的HashMap密钥替换的主要内容,如果未能解决你的问题,请参考以下文章
java 测试的HashMap和ConcurrentHashMap中是否可以存储密钥为空的数据,测试显示前者可以,后者是不可以的,会在看跌的时候校验密钥是否为空,如果为空则会抛出数Npe