为什么在地图中处理GString键的方式有不同的行为?

时间:2015-07-30 00:26:15

标签: groovy maps hashcode gstring

在学习官方文档中的Groovy(2.4.4)语法时,我遇到了有关使用GStrings作为标识符的地图的特殊行为。如文档中所述,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)

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

因此,映射中的键的哈希码与GString哈希码不同。哈!或不。虽然很高兴知道它实际上并不相关,因为我仍然知道map索引中的实际哈希码。我刚刚修改了一个键,它被放入索引后转换为字符串。还有什么呢?

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

无论两个哈希码是否相等,它们都可能不代表地图中的同一个对象。这取决于key-object类的equals方法的实现。例如,如果equals-method没有实现,即使哈希码相同,两个对象也不相等,因此无法正确地处理所需的地图密钥。所以我试过了:

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}"]

1 个答案:

答案 0 :(得分:1)

你可以用一种截然不同的方法来看待这个问题。映射应该具有不可变密钥(至少对于hashcode和equals),因为映射实现依赖于此。 GString是可变的,因此通常不适合地图键。还有调用String#equals(GString)的问题。 GString是一个Groovy类,所以我们可以将equals方法影响到等于String就好了。但是String非常不同。这意味着在Java世界中调用带有GString的String上的equals将始终为false,即使hashcode()在String和GString中的行为相同也是如此。现在想象一个带有String键的地图,并向地图询问带有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()

这里,对于Groovy和GString,地图的toString表示会发生变化,因此GString会产生不同的哈希码