2

在研究官方文档中的 Groovy (2.4.4) 语法时,我遇到了有关使用 GStrings 作为标识符的映射的特殊行为。如文档中所述,GStrings 作为(哈希)映射标识符是一个坏主意,因为非评估 GString 对象的哈希码与具有与评估 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)

建议#1。无论出于何种原因,GString 对象的哈希码工作/实现有点不确定,并根据上下文或实际对象提供不同的结果。

事实证明这是胡说八道:

println "${key}".hashCode() // prints "3392"
// do sth else
println "${key}".hashCode() // still "3392"

建议#2。映射或映射项中的实际键没有预期的表示或哈希码。

我仔细查看了地图中的项目、密钥及其哈希码。

println m // prints "[id:value for id]", as expected
m.each { 
    it -> println key.hashCode() 
} // prints "3355" - hashcode of the String "id"

所以map里面key的hashcode和GString hashcode是不一样的。哈!或不。虽然很高兴知道,但实际上并不相关,因为我仍然知道地图索引中的实际哈希码。我只是重新散列了一个在放入索引后已转换为字符串的键。还有什么?

建议#3。GString 的 equals 方法具有未知或未实现的行为。

无论两个哈希码是否相等,它们都可能不代表一个映射中的同一个对象。这取决于 key-object 类的 equals 方法的实现。例如,如果未实现 equals 方法,即使哈希码相同,两个对象也不相等,因此无法正确寻址所需的映射键。所以我尝试了:

def a = "${key}"
def b = "${key}"

assert a.equals(b)  // returns true (unfortunate but expected)

所以默认情况下,相同 GString 的两个表示是相等的。

我跳过了我尝试过的其他一些想法,并继续我在写这篇文章之前尝试的最后一件事。

建议#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

因此,如果我使用地图的 get 方法,我首先会以我期望的方式获得实际值。

这种关于 GStrings 的地图访问行为的解释是什么?(或者这里隐藏着什么样的菜鸟错误?)

谢谢你的耐心。

编辑:恐怕我的实际问题没有明确说明,所以简明扼要地说:

当我有一个带有 GString 作为键的地图时

def m = ["${key}": "value for ${key}"]

为什么这会返回值

println m.get("${key}")

但这并不

println m["${key}"]

?

4

1 回答 1

1

你可以用一种非常不同的方法来看待这个问题。map 应该有不可变的键(至少对于 hashcode 和 equals),因为 map 的实现依赖于此。GString 是可变的,因此通常不适合映射键。还有调用String#equals(GString)的问题。GString 是一个 Groovy 类,因此我们可以将 equals 方法影响为等于 String 就好了。但是字符串非常不同。这意味着在 Java 世界中,使用 GString 对 String 调用 equals 将始终为 false,即使 hashcode() 对 String 和 GString 的行为相同。现在想象一个带有字符串键的映射,并且您向映射请求一个带有 GString 的值。它总是返回 null。另一方面,使用字符串查询的具有 GString 键的映射可以返回“正确”值。

由于这个问题,GString#hashCode() 故意不等于 String#hashCode()。

它绝不是不确定的,但是如果参与的对象更改了它们的 toString 表示,则 GString 哈希码可以更改:

def map = [:]
def gstring = "$map"
def hashCodeOld = gstring.hashCode()
assert hashCodeOld == gstring.hashCode()
map.foo = "bar"
assert hashCodeOld != gstring.hashCode()

这里 map 的 toString 表示会随着 Groovy 和 GString 改变,因此 GString 会产生不同的哈希码

于 2015-07-30T09:39:15.973 回答