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