Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy/Grails LinkedHashMap behaving weirdly

I'm running into some confusing behaviour from LinkedHashMap in grails 2.0.3. Running the following script in grails console:

def m = ["smart-1":[stuff:'asdf']]
println m.getClass()

def p = [id:1]
println m."smart-$p.id"
println m["smart-$p.id"]
println m.get("smart-$p.id")

println m.'smart-1'
println m['smart-1']
println m.get('smart-1')

gives the output:

class java.util.LinkedHashMap
[stuff:asdf]
[stuff:asdf]
null
[stuff:asdf]
[stuff:asdf]
[stuff:asdf]

In an integration test, I'm seeing the opposite behaviour - I can only get the content of the HashMap using m.get(GStringImpl) (as opposed m.get(String)).

Is this behaviour expected or known?

like image 989
Alex Avatar asked Nov 30 '22 23:11

Alex


1 Answers

First: don't use GStrings in your hashmap keys. Ever. You will almost always have an issue retrieving the item, because a GString is not a String (red box on that page), and does not have the same hash value. Instead, use one of these options:

def key = 'key'
['key': value]
[(key): value]
[("some $key".toString()): value]

This ensures that you will always get the result when using a String. (So, for lookups, always use a String, too.)

I'm not 100% sure why you are seeing the odd behavior, but I have a solid guess. The get() method is a Java method, while the array-style (and probably property-style) lookup is implemented using getAt(), which is a Groovy (GDK) method. My guess is that the Groovy method is aware of GStrings, and silently handles the conversion to make sure you don't get tripped up.

The simplest solution is to always use getAt(), not get:

def m = ['smart-1':[stuff:'asdf']]
println m.getClass()

def p = [id:1]
println m."smart-$p.id"
println m["smart-$p.id"]
println m.getAt("smart-$p.id")

println m.'smart-1'
println m['smart-1']
println m.getAt('smart-1')

Which works fine.

The better solution is to ensure that you are using Strings when looking up values, like so:

println m.get("smart-$p.id".toString())

Which also works. I like this method better, because when calling the method directly, it's clearer that your key is a String. I'd still use the normal GString when using the array-style or property-style accessors, because that's standard Groovy syntax.


In an integration test, I'm seeing the opposite behaviour - I can only get the content of the HashMap using m.get(GStringImpl) (as opposed m.get(String)).

This is most likely because your key in your hashmap is staying a GString.

If a GString does not have any variables, the Groovy compiler silently converts it to a String literal (better performance), which is why the above example is actually using a String as the key, but the lookup is using a GString.

e.g.

"Hello $name" -> GString('Hello $name')
"Hello Bob"   -> 'Hello Bob'

A final thought: As long as you are in groovy, don't use get(), since Groovy provides the much cleaner [] and property syntaxes.

like image 180
OverZealous Avatar answered Dec 05 '22 00:12

OverZealous