Mapの初期化でKeyをGStringにした場合、getメソッドを使用しないと値を取得できない。

表題の通り。
Mapの初期化時には変数名を直接指定できないので、GStringで対応していたらハマった。

  • っというか、元々はcollectEntriesイジっててハマったのでcollectEntriesがおかしいって記事まで書いてた。。。

環境

$ groovy -version
Groovy Version: 2.2.2 JVM: 1.8.0 Vendor: Oracle Corporation OS: Windows 7
  • 2.3.0-beta-2でも発生しました。

現象

言葉で説明は下手くそなのでコードで。

def key1 = "a"

// 変数は直接指定できない(文字列のKeyとして処理される)
def map1 = [key1:"a"]
assert map1 == ["key1":"a"]

// GStringは別のKeyとして認識される
def map2 = ["${key1}":"a"]
assert map2 != ["a":"a"]
assert map2 == ["${'a'}":"a"]
assert map2.keySet()[0].class == org.codehaus.groovy.runtime.GStringImpl

// StringとGStringの型
def key2 = "${key1}"
assert key1.class == java.lang.String
assert key2.class == org.codehaus.groovy.runtime.GStringImpl

// containsKeyはGStringで格納されているとの事。
assert !map2.containsKey(key1)
assert map2.containsKey(key2)

// StringでもGStringでも取得できない
assert map2[(key1)] == null
assert map2[(key2)] == null
assert map2.(key1) == null
assert map2.(key2) == null

// 直接getメソッドを呼ぶと取得が可能
assert map2.get("a") == null
assert map2.get("${'a'}") == "a"

つまり、GStringを使用してMapを初期化するとStringではなくてGStringとして格納されるため、Stringで取得しようとしてもダメ。
では、GStringで指定しても[]の呼び出しは(getAtメソッド)Groovyがいい感じにStringに変換してくれているようなのでこれもダメ。
Javaメソッドのgetを直接呼ぶことでGStringをそのまま渡せるので習得ができます。

初期化以外の処理では、GStringはStringに変換されて格納されるため、格納時にも取得時にも問題は発生しません。

def key1 = "a"
def key2 = "${key1}"
// 初期化以外では変数はそのまま指定可能
// GStringもStringに変換されて格納される。
def map3 = [:]
map3[key1] = "a"
map3[key2] = "b"
assert map3.size() == 1
assert map3[key1] == "b"
assert map3[key2] == "b"
assert map3.keySet()[0].class == java.lang.String
assert map3.get(key1) == "b"
assert map3.get(key2) == null

正直この挙動はどうなんでしょう。。。?

回避策

変数使いたいだけなら()を使用する事で回避可能。
文字列の結合が必要ならば「as String」でStringに変換する。

def key1 = "a"
def map4 = [(key1):"a"]
assert map4 == ["a":"a"]
assert map4 != ["${'a'}":"a"]
assert map4[key1] == "a"
assert map4[key2] == "a"

def map5 = [("${key1}_${key2}" as String):"a"]
assert map5 == ["a_a":"a"]
assert map5 != ["${'a_a'}":"a"]
assert map5["a_a"] == "a"
assert map5["${'a_a'}"] == "a"

参考

ここまで色々回り道して調べたんですが、下書き書いてからもう一回ググったらふもさん(id:fumokmm)のblogが出てきた。
5年前に調べてくれてるじゃないですかーやだー
しかも、id:uehajさんがJIRAまで調べてくれてるじゃないですかー

Groovyでマップのキーに文字列を指定する際の注意点 - No Programming, No Life

5年前でなおってないなら仕様ですね。
これわかりづらいと思うんですが。。。