0x01 问题来源
在Groovy中使用集合时出现一个问题:
class A {
String name
int age
}
A a = new A()
a.name = 'zhangsan'
a.age = 12
Map<String, A> map = new HashMap<>()
map["${a.name}:${a.age}"] = a
println map.containsKey("${a.name}:${a.age}")
运行结果如下:
> class A {
> String name
> int age
> }
>
> A a = new A()
> a.name = 'zhangsan'
> a.age = 12
>
> Map<String, A> map = new HashMap<>()
> map["${a.name}:${a.age}"] = a
>
> println map.containsKey("${a.name}:${a.age}")
false
什么? 集合里面竟然不包含 a ? 纳尼...
0x02 问题验证
查看 map.containsKey()
源码可知:
/**
* Returns <tt>true</tt> if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
该方法对 key 进行了hash 计算. 那么验证一下哈希值呗.
在Groovy 中执行一下代码:
class A {
String name
int age
}
A a = new A()
a.name = 'zhangsan'
a.age = 12
assert "${a.name}:${a.age}".hashCode() == "zhangsan:12"
执行结果如下:
> class A {
> String name
> int age
> }
>
> A a = new A()
> a.name = 'zhangsan'
> a.age = 12
>
> assert "${a.name}:${a.age}".hashCode() == "zhangsan:12"
Assertion failed:
assert "${a.name}:${a.age}".hashCode() == "zhangsan:12"
| | | | | |
| zhangsan| 12 367749900 false
| A@72d1ad2e
A@72d1ad2e
哈希值还真的不一样!!!!
0x03 查找源头
没办法了,只能去查 groovy的文档,终于在文档中找到这样一段话:
4.4.4. GString and String hashCodes
Although interpolated strings can be used in lieu of plain Java strings, they differ with strings in a particular way: their hashCodes are different. Plain Java strings are immutable, whereas the resulting String representation of a GString can vary, depending on its interpolated values. Even for the same resulting string, GStrings and Strings don’t have the same hashCode.
assert "one: ${1}".hashCode() != "one: 1".hashCode()
GString and Strings having different hashCode values, using GString as Map keys should be avoided, especially if we try to retrieve an associated value with a String instead of a GString.
def key = "a"
def m = ["${key}": "letter ${key}"]
assert m["a"] == null
The map is created with an initial pair whose key is a GString
When we try to fetch the value with a String key, we will not find it, as Strings and GString have different hashCode values
英文不太好,找Google翻译一下吧:
虽然内插字符串可以用来代替普通的Java字符串,但是它们以特定的方式与字符串不同:它们的hashCodes是不同的。纯Java字符串是不可变的,而GString的结果String表示可以根据其内插值而有所不同。即使对于相同的结果字符串,GStrings和Strings也没有相同的hashCode。
大概意思就是GString是可变的,String是不可变的,所以他俩哈希值不一样!
真是血的教训啊, 没搞清楚直接使用. 希望以本文为戒.
评论区