Skip to content

Groovy Map Surprises

I lost a fair bit of time today wrestling with the subtleties of Groovy Maps.

Take a look:

def val = 99
def map = [:]

map = ["fred$val":val]
println map["fred$val"]
// => null

map = [("fred$val"):val]
println map["fred$val"]
// => null

map = [("fred$val".intern()):val]
println map["fred$val"]
// => 99

// compilation error...ok, expected
//map = ['fred' + val:val]
//println map["fred$val"]

map = [('fred' + val):val]
println map["fred$val"]
// => 99

map = [("fred$val".toString()):val]
println map["fred$val"]
// => 99

def s = "fred$val"
map = [(s):val]
println map["fred$val"]
// => null

s = "fred$val"
map = [(s):val]
println map[s]
// => null

map = [:]
map.put("fred$val", 99)
println map.get("fred$val")
// => 99

The amount of strangeness shown here is all too much for my liking…

On leafing through the Groovy ‘bible’, GINA, I found the possibility of a hint of a reason:

There is also a corner case with GStrings if their values write themselves lazily.

Which led me to the Groovy site on Strings and GStrings, which gives the salient advice:

GStringsAreNotStrings

So, now I know.

It’s partly (mostly?) my misconception…or foolishness: expecting a String to be a GString.

Well:

How can I possibly put a new idea into your heads, if I do not first remove your delusions?
“Doctor Pinero” in Life-Line (1939)

I now carrying one less delusion around with me. This is good.

Not sure I am any happier, TANJ it!

Here’s a debugging nightmare:

final val = 99

println """
GString:"""
def map = [:]
// NB: without toString()
map.put("fred$val", '?')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
  println "$k=>$v"
  }

println """
String:"""
map = [:]
// NB: with toString()
map.put("fred$val".toString(), '!')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
  println "$k=>$v"
  }

println """
Buyer beware:"""
map = [:]
map.put("fred$val", val)
map.put("fred$val".toString(), val)
map.each {k,v ->
  println "$k=>$v"
  }

Here’s what you get:

GString:
?
null
null
null
null
null
null
null
null
null
null
null
fred99=>?

String:
null
!
!
!
!
!
!
!
!
!
!
!
fred99=>!

Buyer beware:
fred99=>99
fred99=>99
Result: [fred99:99, "fred99":99]

TANSTAAFL, I guess…you win some, you lose some and this is but a small entry on Groovy’s otherwise clean “rap sheet.”

[edit]

Jim Shingler (who writes at: Shingler’s Thoughts) sent me the following alternative:

def val = 99
def map = [:]

map."fred$val" = val
println map
println map["fred$val"]
println map."fred$val"

Seems to be a little more robust, thanks.

The takeaway message seems (to me) to be: “Don’t use GStrings as keys in a map. Especially don’t use GStrings with embedded placeholders because their lazy evaluation may make life awkward.”

Tags:

Java Enterprise Edition, JEE, JavaServer Pages, JSP, Tag Libraries, Servlets, Enterprise Java Beans, EJB, Java Messaging Service JMS, BEA Weblogic, JBoss, Application Servers, Spring Framework, Groovy, Grails, Griffon, Seam, Open Source, Service Oriented Architectures, SOA, Java 2 Standard Edition, J2SE