问题描述
在研究官方文档中的Groovy(2.4.4)语法时,我遇到了有关使用GStrings作为标识符的地图的特殊行为。如文档中所述,GStrings作为(散列)映射标识符是一个坏主意,因为未评估的GString对象的哈希码与具有与所评估的GString相同表示形式的常规String对象的哈希码不同。 b$ b
例子:
def key =id
$ p $然而,我直觉的预期是,使用实际的GString标识符来处理地图中的某个键实际上会提供这个值 - 但它不会。 $ b
def m = [ $ {key}:value for $ {key}]
printlnid.hashCode()//打印3355
println$ {key}。 hashCode()//输出3392,不同的散列码
assert m [id] == null //评估真实
$ bdef key =id
def m = [$ {key}:$ {key}的值]
assert m [$ {key}] == null //计算结果也为true,不是预期的
这让我很好奇。所以我就这个问题提出了几点建议,并做了一些实验。
(请记住我是Groovy的新手,我只是随时进行头脑风暴 - 如果您不想继续使用建议#4阅读我是如何检查问题的原因的)
建议#1。有些是不确定的,无论出于何种原因,并根据上下文或实际对象提供不同的结果。
结果很荒谬:
println$ {key}。hashCode()//输出3392
// do sth else
println $ {key}。hashCode()//仍然是3392
建议#2 。地图或地图项目中的实际关键字没有预期的表示形式或散列码。
我仔细看了一下地图中的项目,键和它的哈希码。
println m //打印出[id:value for id],如预期的那样
m.each {
i t - > println key.hashCode()
} //打印3355 - 字符串ID的散列码
因此,映射内部密钥的哈希码与GString哈希码不同。哈!或不。虽然很高兴知道,但它实际上并不相关,因为我仍然知道地图索引中的实际哈希码。我刚刚重新整理了一个已被转换为字符串的密钥,并将其放入索引中。那么还有什么?
建议#3。 GString的equals方法有一个未知或未实现的行为。
无论两个哈希码是否相等,它们可能不会在地图中表示相同的对象。这取决于key-object类的equals方法的实现。例如,如果equals方法未实现,则即使哈希码是相同的,两个对象也不相等,因此无法正确处理所需的映射关键字。所以我试过了:
$ pre $ def $ = $ {key}
def b =$ {key}
assert a.equals(b)//返回true(不幸但是预期)
因此,相同的GString的两个表示在默认情况下是相等的。
我跳过一些其他的想法,我尝试着继续我在写这篇文章之前尝试的最后一件事。
建议#4。访问的语法很重要。
这是一个真正的理解杀手。我之前就知道:两种访问映射值在语法上有所不同。每种方式都有其限制,但我认为结果保持不变。那么,这就出现了:
def key =id
def m = [$ {key} :value for $ {key}]
assert m [id] == null //如前
assert m [$ {key}] == null / / as
assert m.get($ {key})== null //断言失败,返回值
因此,如果我使用地图的get方法,我会以我预期的方式获得实际值。
有关GStrings的地图访问行为的解释是什么? (或者是什么样的菜鸟错误隐藏在这里?)
感谢您的耐心。
编辑:我恐怕我的实际问题没有明确说明,所以这里是简短和简洁的例子:
当我有一个GString的地图作为这样的关键字时
def m = [$ {key}:$ {key}的值]
为什么会这样返回值
$ p $ println m.get($ {key})
但那不是
println m [ $ {key}]
?
你可以用一种非常不同的方法来看待这个问题。地图应该有不可变的键(至少对于hashcode和equals),因为地图的实现依赖于这个。 GString是可变的,因此通常不适合映射键。还有调用String#equals(GString)的问题。 GString是一个Groovy类,所以我们可以影响equals方法等于一个String就好了。但是String非常不同。这意味着即使hashcode()对于String和GString表现相同,那么在Java世界中,对于GString字符串调用equals也将始终为false。现在想象一下带有String键的地图,然后用GString向地图询问一个值。它总是会返回null。另一方面,用String查询GString键的地图可能会返回正确值。这意味着总会有一个断开。
由于这个问题,GString#hashCode()并不等于String#hashCode()。
它绝不是非确定性的,但如果参与对象改变它们的toString表示,GString哈希码可以改变:
<$
def gstring =$ map
def hashCodeOld = gstring.hashCode()
assert hashCodeOld == gstring.hashCode ()
map.foo =bar
assert hashCodeOld!= gstring.hashCode()
这里map的toString表示将会改变为Groovy和GString,因此GString会产生一个不同的哈希码
While studying the Groovy (2.4.4) syntax in the official documentation, I came across the special behavior concerning maps with GStrings as identifiers. As described in the documentation, GStrings are a bad idea as (hash)map identifiers, because the hashcodes of a non-evaluated GString-object differs from a regular String-object with the same representation as the evaluated GString.
Example:
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
However, my intuitive expectation was that using the actual GString identifier to address a key in the map will in fact deliver the value - but it does not.
def key = "id"
def m = ["${key}": "value for ${key}"]
assert m["${key}"] == null // evaluates also true, not expected
That made me curious. So I had several suggestions concerning this issue and did some experiments.
(pls keep in my mind that I am new to Groovy and I was just brainstorming on the fly - continue to Suggestion #4 if you do not want to read how I tried to examine the cause of the issue)
Suggestion #1. hashcode for GString objects works/is implemented somewhat non-deterministic for whatever reason and delivers different results depending on the context or the actual object.
That turned out to be nonsense quite fast:
println "${key}".hashCode() // prints "3392"
// do sth else
println "${key}".hashCode() // still "3392"
Suggestion #2. The actual key in the map or the map item does not have the expected representation or hashcode.
I took a closer look at the item in the map, the key, and its hashcode.
println m // prints "[id:value for id]", as expected
m.each {
it -> println key.hashCode()
} // prints "3355" - hashcode of the String "id"
So the hashcode of the key inside the map is different from the GString hashcode. HA! or not. Though it is nice to know, it is actually not relevant because I still do know the actual hashcodes in the map index. I just rehashed a key that has been transformed to a string after being put into the index. So what else?
Suggestion #3. The equals-method of a GString has an unknown or non- implemented behavior.
No matter whether two hashcodes are equal, they may not represent the same object in a map. That depends on the implementation of the equals method for the class of the key-object. If the equals-method is, for instance, not implemented, two objects are not equal even if the hashcode is identical and therefore the desired map key cannot be adressed properly. So I tried:
def a = "${key}"
def b = "${key}"
assert a.equals(b) // returns true (unfortunate but expected)
So two representations of the same GString are equal by default.
I skip some others ideas I tried and continue with the last thing I tried just before I was going to write this post.
Suggestion #4. The syntax of access matters.
That was a real killer of understanding. I knew before: There are syntactically different ways two access map values. Each way has its restrictions, but I thought the results stay the same. Well, this came up:
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
So if I use the get-method of a map, I get the actual value in the way I expected it to in the first place.
What is the explanation for this map access behavior concerning GStrings? (or what kind of rookie mistake is hidden here?)
Thanks for your patience.
EDIT: I am afraid that my actual question is not clearly stated, so here is the case in short and concise:
When I have a map with a GString as a key like this
def m = ["${key}": "value for ${key}"]
why does this return the value
println m.get("${key}")
but that does not
println m["${key}"]
?
You can look at this matter with a very different approach. A map is supposed to have immutable keys (at least for hashcode and equals), because the map implementation depends on this. GString is mutable, thus not really suited for map keys in general. There is also the problem of calling String#equals(GString). GString is a Groovy class, so we can influence the equals method to equal to a String just fine. But String is very different. That means calling equals on a String with a GString will always be false in the Java world, even if hashcode() would behave the same for String and GString. And now imagine a map with String keys and you ask the map for a value with a GString. It would always return null. On the other hand a map with GString keys queried with a String could return the "proper" value. This means there will always be a disconnection.
And because of this problem GString#hashCode() is not equal to String#hashCode() on purpose.
It is in no way non-deterministic, but a GString hashcode can change, if the participating objects change their toString representation:
def map = [:]
def gstring = "$map"
def hashCodeOld = gstring.hashCode()
assert hashCodeOld == gstring.hashCode()
map.foo = "bar"
assert hashCodeOld != gstring.hashCode()
Here the toString representation of map will change for Groovy and GString, thus the GString will produce a different hashcode
这篇关于为什么在地图中寻址GString键的方式有不同的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!