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是不可变的,所以他俩哈希值不一样!

真是血的教训啊, 没搞清楚直接使用. 希望以本文为戒.