Saturday, January 1, 2011

GStringの扱いに注意

Groovyは文字列用に2つの型、StringとGStringを持ちます。 通常これらは透過的に扱えます。以下の例を見てください。
----
def s = "1"
println s
println s.class.name
def gs = "${1}"
println gs
println gs.class.name
println s == gs
----

この結果は以下になります。
----
1
java.lang.String
1
org.codehaus.groovy.runtime.GStringImpl
true
----

しかし例外があります。 次の例ではオフィサエージェントのペアをシャッフルしようとしています(彼らは漫画One Pieceのキャラクタです)。
----
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)
----

結果は次のようになります。
----
[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]
----

うまくシャッフルできました。ではMr. 0のパートナを出力してみましょう。
----
println officerAgentPairs.get("Mr. 0")
----

予想に反し、結果はこうなります。
----
null
----

なぜでしょうか? まず、StringとGStringは同じ文字列を表していても、同じハッシュコードは持ちません。GStringは可変なためです。次に、GroovyはHashMapのput()メソッドをGStringをキーとして扱えるように拡張していません(これは既知の問題で、Groovyチームはwon't fixにしています)。このため、自分自身でGStringをStringへ変えてあげる必要があります。

上記の例の中でGStringをStringへ変える方法はいくつかあります。


添え字かputAt()メソッドをput()メソッドの代わりに使う:
----
pairs["Mr. $no"] = femaleAgents[no]
----
----
pairs.putAt("Mr. $no", femaleAgents[no])
----

toString()メソッドを使う:
----
pairs.put("Mr. $no".toString(), femaleAgents[no])
----

asキーワードを使う:
----
pairs.put("Mr. $no" as String, femaleAgents[no])
----

Stringとして静的に型付けされた変数へ代入する:
----
String maleAgent = "Mr. $no"
pairs.put(maleAgent , femaleAgents[no])
----

個人的には添え字を使う最初の方法を推奨します。よりGroovy的ですし、上記の方法の中ではもっとも短いです。

      

No comments:

Post a Comment