在官方文档中研究Groovy(2.4.4)语法时,我遇到了关于使用GStrings作为标识符的 Map 的特殊行为 . 如文档中所述,GStrings作为(哈希)映射标识符是一个坏主意,因为未评估的GString对象的哈希码与常规String对象的不同,其表示与评估的GString相同 .
例:
def key = "id"
def m = ["${key}": "value for ${key}"]
println "id".hashCode() // prints "3355"
println "${key}".hashCode() // prints "3392", different hashcode
assert m["id"] == null // evaluates true
但是,我的直观期望是使用实际的GString标识符来解决映射中的键实际上会传递值 - 但事实并非如此 .
def key = "id"
def m = ["${key}": "value for ${key}"]
assert m["${key}"] == null // evaluates also true, not expected
这让我很好奇 . 所以我有几个关于这个问题的建议,并做了一些实验 .
(请记住,我是Groovy的新手,我只是动态地集思广益 - 如果你不想阅读我是如何尝试检查问题的原因那么继续建议#4)
3712924 GString对象的哈希码工作/由于某种原因在某种程度上是非确定性的,并根据上下文或实际对象提供不同的结果 .
结果证明这是胡说八道:
println "${key}".hashCode() // prints "3392"
// do sth else
println "${key}".hashCode() // still "3392"
Suggestion #2. Map 或 Map 项中的实际键没有预期的表示形式或哈希码 .
我仔细研究了 Map 中的项目,密钥和哈希码 .
println m // prints "[id:value for id]", as expected
m.each {
it -> println key.hashCode()
} // prints "3355" - hashcode of the String "id"
因此,映射中的键的哈希码与GString哈希码不同 . 哈!或不 . 虽然很高兴知道它实际上并不相关,因为我仍然知道map索引中的实际哈希码 . 我刚刚修改了一个键,它被放入索引后转换为字符串 . 还有什么?
Suggestion #3. GString的equals方法具有未知或未实现的行为 .
无论两个哈希码是否相等,它们都可能不代表 Map 中的同一个对象 . 这取决于key-object类的equals方法的实现 . 例如,如果equals-method没有实现,即使哈希码相同,两个对象也不相等,因此无法正确地处理所需的 Map 密钥 . 所以我尝试过:
def a = "${key}"
def b = "${key}"
assert a.equals(b) // returns true (unfortunate but expected)
因此,默认情况下,同一GString的两个表示相等 .
我跳过其他一些我试过的想法,并继续我在写这篇文章之前尝试的最后一件事 .
Suggestion #4. 访问语法很重要 .
这是理解的真正杀手 . 我之前知道:两种访问映射值的语法不同 . 每种方式都有其限制,但我认为结果保持不变 . 嗯,这出现了:
def key = "id"
def m = ["${key}": "value for ${key}"]
assert m["id"] == null // as before
assert m["${key}"] == null // as before
assert m.get("${key}") == null // assertion fails, value returned
因此,如果我使用 Map 的get方法,我会以我预期的方式获得实际值 .
有关GStrings的这种 Map 访问行为的解释是什么? (或者这里隐藏着什么样的菜鸟错误?)
谢谢你的耐心 .
EDIT :我担心我的实际问题没有明确说明,所以这里的案例简明扼要:
当我有一个GString作为这样的键的 Map
def m = ["${key}": "value for ${key}"]
为什么这会返回 Value
println m.get("${key}")
但事实并非如此
println m["${key}"]
?
1 回答
你可以用一种非常不同的方法来看待这个问题 . 映射应该具有不可变密钥(至少对于hashcode和equals),因为映射实现依赖于此 . GString是可变的,因此通常不适合 Map 键 . 还有调用String#equals(GString)的问题 . GString是一个Groovy类,所以我们可以将equals方法影响到等于String就好了 . 但是String非常不同 . 这意味着在Java世界中调用带有GString的String上的equals将始终为false,即使hashcode()在String和GString中的行为相同也是如此 . 现在想象一个带有String键的 Map ,并向 Map 询问带有GString的值 . 它总是返回null . 另一方面,使用String查询的带有GString键的映射可以返回“正确”值 . 这意味着将始终断开连接 .
由于这个问题,GString#hashCode()有意不等于String#hashCode() .
它绝不是非确定性的,但如果参与对象更改其toString表示,则GString哈希码可以更改:
这里,对于Groovy和GString,map的toString表示将发生变化,因此GString将产生不同的哈希码