LoginSignup
5
3

More than 5 years have passed since last update.

Groovy の GString と 遅延評価

Last updated at Posted at 2016-11-29

Groovy の GString

Groovy では、文字列の中に「${ ~ }」のように記述することで式を埋め込むことができます。このとき、Groovy はその文字列リテラルを GString オブジェクトとして扱います。ただし、String が必要な箇所で GString を渡すと勝手に String に変換してくれるので、あまり意識することはないでしょう。また、「\${ ~ }」の代わりに「\${-> ~ }」とすると、式を遅延評価させることができます。

さて、以下のコードを見てください。

// array プロパティをもつオブジェクトを作成
def test = new Expando(array: [0]) 

// String として生成
String string = "string: ${test.array}"
// GString として生成
def direct = "direct: ${test.array}"
// GString として遅延評価によって生成
def lazy = "lazy: ${-> test.array}"

test.array << 1   // (1)
test.array = [2]  // (2)

println(string)
println(direct)
println(lazy) 

これを実行すると、以下のようになります。

string: [0]
direct: [0, 1]
lazy: [2]

全部結果が違います。これについて説明したいと思います。

まず大前提として、GString に埋め込まれた遅延評価でない式の中で出てくる変数は、全てその参照として保持されます。したがって、 "direct: ${user.array}" が実行された時点で、user オブジェクトの array プロパティの参照が、GString クラスの内部に保持されます。この参照を仮に A としましょう。

その後、コードの (1) の部分で、それと同じ参照 A をもつ変数に要素が加えられます。さらにコード (2) の部分で array プロパティに [2] というオブジェクトが代入されていますが、この [2] はこの場で作られた新たなオブジェクトなので、[2] の参照と A は異なります。したがって、direct に代入された GString が内部に保持している参照 A には何の影響もありません。したがって、最終的に println(direct) の実行結果は、要素が 1 つ追加されて「direct: [0, 1]」となるわけです。

GStringString に変換された時点で、内部に保持している参照に対して toString メソッドを呼び、文字列として確定させます。したがって、上のコードの string"string: ${user.array}" が代入された時点で、user オブジェクトの array プロパティに対して toString が呼び出されます。この結果は当然「[0]」なので、string には "string: [0]" という String オブジェクトが代入されることになります。この時点で String オブジェクトとして固定されたため、他の変数に変更が加えられたり代入し直されたりしても何の影響も受けず、最終的に println(string) の実行結果は「string: [0]」となります。

最後に、遅延評価にした GString の場合ですが、これは変数の参照を保持するのではなくクロージャとして保持します。そして、toString が呼び出されるたびに、保持しているクロージャを評価し、その結果に置き換えます。したがって、println(lazy) を実行すると、その時点で "lazy: ${-> user.array}" 内の user.array が実行され、その結果に置き換えられた文字列が表示されます。この時点で user.array は (2) の箇所で [2] に置き換えられているので、これが評価されて「lazy: [2]」と表示されるわけです。

ちなみに Ruby では

Ruby にも文字列への式の埋め込みができるのですが、こちらはその場で評価されて文字列に展開されます。したがって、後から変数が変更されたり再代入されても影響を受けません。

test = Struct.new(:array).new([0])

string = "string: #{test.array}"

test.array << 1
p(string)
test.array = 2
p(string)

これの実行結果は以下のようになります。

"string: [0]"
"string: [0]"
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3