Nik Nik - 3 months ago 8
Groovy Question

Groovy Strings vs Java Strings

I have a string in groovy that I make from other strings, example:

def final PREFIX = "myprefix"
def prefix2 = PREFIX + ".whatever1"


Now suppose I have a
HashMap
and I want to do a lookup using
prefix2
as part of a key:

HashMap<String,String> map = new HashMap<String,String>()
map.put("myprefix.whatever1.value","aaa")


If I do:

def key = "${prefix2}.value"
String result=(map.get(key))


Then
result = null
, but if I do:

String key="${prefix2}.value"
String result=(map.get(key))


Then
result = aaa
.

I can understand why this happens, obviously a type inference issue. But I still find it makes me feel icky. Something just doesn't "feel right" about it. Do you know what I mean?

Is this type of thing normal and should be "expected"? Am I asking for too much for Groovy to know that if I created the string using quotes, it should work when being used to look up a value in a
Hashmap <String, String>
without being defined as a
String
object reference? Is this a bug or a feature?

Answer

There are multiple things that need to be take into consideration why this is happening.

When you create a variable HashMap<String,String> map you would expect that like in java you would only be able to add strings as keys and values to it. This is not the case in groovy as the type arguments will not be taken into consideration which means the following will work:

HashMap<String,String> map = new HashMap<String,String>()
map.put(1, 2)   
assert map.get(1) == 2
assert map.get(1) instance of Integer

Also as you stated, an interpolated string like "${prefix2}.value" is an instance of GString (GStringImpl to be specific) so the following is true

def key = "${prefix2}.value"
assert key instanceof GString

So, map.get(key) will get invoked with a GString parameter without an error since the String constraint is dropped, which would not be an issue if the GStringImpl would give the same hashcode as the String, but it doesn't

assert key == "myprefix.whatever1.value"
assert key.hashCode() != "myprefix.whatever1.value".hashCode()

This is why the get on the map returns a null

The way to work around this is, as you stated using either toString() or assigning the GString to a String variable (toString() is called implicitly)

Another way, which I would prefer, is to not mix the usual java style map access with the groovy one. If you use the property notation or key indexed on the map using the GString it will work perfectly:

def final PREFIX = "myprefix"
def prefix2 = PREFIX + ".whatever1"

// def map = [:] // shorter will be a LinkedHashMap
HashMap<String,String> map = [:] as HashMap // if you really need HashMap
map."myprefix.whatever1.value" = "aaa"

def key = "${prefix2}.value"

assert map[key] == "aaa"
assert map."${prefix2}.value" == "aaa"
assert map."$key" == "aaa"
Comments