About Me

My photo
Author of Groovy modules: GBench, GProf, Co-author of a Java book パーフェクトJava, Game Developer at GREE

Saturday, January 1, 2011

Be careful using GString

Groovy has two types for strings, String and GString. Normally, you can use them transparently. See the example below.
----
def s = "1"
println s
println s.class.name
def gs = "${1}"
println gs
println gs.class.name
println s == gs
----

The result will be below.
----
1
java.lang.String
1
org.codehaus.groovy.runtime.GStringImpl
true
----

But there are exceptions. In the next example, I try to shuffle the officer agent pairs (They are the characters from the One Piece manga).
----
def pairOfficerAgents(List femaleAgents) {
    def pairs = [:]
    (0..5).each{ no ->
        pairs.put("Mr. $no", femaleAgents[no]) 
    }
    pairs
}

def femaleAgents = ["Ms. All Sunday", "Ms. Doublefinger", "None", "Ms. Golden Week", "Ms. Merry Christmas", "Ms. Valentine"]
def officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs)
println("Shuffle!")
Collections.shuffle(femaleAgents)
officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs
----

The result will be like this.
----
[Mr. 0:Ms. All Sunday, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. Golden Week, Mr. 4:Ms. Merry Christmas, Mr. 5:Ms. Valentine]
Shuffle!
[Mr. 0:Ms. Merry Christmas, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. All Sunday, Mr. 4:Ms. Golden Week, Mr. 5:Ms. Valentine]
----

OK, they are successfully shuffled. Then I will try to print out the partner of the Mr. 0.
----
println officerAgentPairs.get("Mr. 0")
----

Contrary to your expectations, the result will be this.
----
null
----

Why? First, String and GString do not have the same hash code even if they express the same string because GString is mutable. Second, Groovy doesn't extend HashMap's put() method to be able to use GString as key (This is the known issue that the Groovy team says "won't fix"). So you need to make GString into String by yourself.  

There are some ways to make GString into String in the example above.

Use subscript operator or putAt() method instead of put() method:
----
pairs["Mr. $no"] = femaleAgents[no]
----
----
pairs.putAt("Mr. $no", femaleAgents[no]) 
----

Use toString() method:
----
pairs.put("Mr. $no".toString(), femaleAgents[no])
----

Use the as keyword:
----
pairs.put("Mr. $no" as String, femaleAgents[no])
----

Assign to a variable that is statically typed as String:
----
String maleAgent = "Mr. $no"
pairs.put(maleAgent , femaleAgents[no])
----

I personally recommend the first way, using subscript operator. Because it's more Groovy style, and it's the shortest among the ways above. 

   

2 comments:

  1. Actually, it's recommended to avoid using GStrings as keys because of the very reason you exposed: they are mutable. Map keys should be immutable as most as possible. This is one of the reasons why the issue is marked as "won't fix".

    ReplyDelete
  2. >Map keys should be immutable as most as possible.
    I agree it.

    I think the point is:
    We can use String and GString transparently in most cases, but not in all cases.

    ReplyDelete