为什么在映射中寻址GString键的方式有不同的行为?



在官方文档中学习Groovy(2.4.4)语法时,我遇到了使用gstring作为标识符的映射的特殊行为。如文档中所述,GString作为(哈希)映射标识符是一个坏主意,因为未求值的GString对象的哈希码与与求值的GString具有相同表示的常规string对象的哈希码不同。

的例子:

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。映射或映射项中的实际键没有预期的表示形式或哈希码。

我仔细查看了map中的项、键和它的哈希码。

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

所以map中键的哈希码不同于GString哈希码。哈!与否。虽然知道这一点很好,但它实际上并不相关,因为我仍然知道map索引中的实际哈希码。我只是重新散列了一个键,该键在放入索引后已转换为字符串。还有什么?

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

无论两个哈希码是否相等,它们都不能在映射中表示相同的对象。这取决于键对象类的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

因此,如果我使用map的get方法,我将以我最初期望的方式获得实际值。

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

谢谢你的耐心。

EDIT:恐怕我的实际问题没有说清楚,所以简短扼要地说一下情况:

当我有一个像这样的GString作为键的map

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

为什么返回值

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

但不

println m["${key}"]

?

您可以用一种非常不同的方法来看待这个问题。映射应该具有不可变的键(至少对于hashcode和equals),因为映射的实现依赖于此。GString是可变的,因此通常不适合映射键。调用string# equals(GString)也存在问题。GString是一个Groovy类,所以我们可以影响equals方法使其等于一个String。但是String是非常不同的。这意味着在Java世界中,对带有GString的String调用equals将始终为false,即使hashcode()对String和GString的行为相同。现在想象一个带有String键的map,你用GString请求map的值。它总是返回null。另一方面,使用String查询带有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将产生一个不同的hashcode

相关内容

  • 没有找到相关文章

最新更新