[はじめようApache Groovy] #15 - クロージャ

こんにちは!なるーらぼです!前回まではオブジェクト指向プログラミングの要素としてトレイトをみてきました。振る舞いの合成はかなり強力な機能ですね!

最後にGroovyでご紹介するのはこれまでも何度か登場しているクロージャについてです。


クロージャ

クロージャというのはいろんなプログラミング言語において登場する便利なやつです。Groovyにおいてクロージャは波括弧で囲まれたコードブロックで、引数もとることができます。しかも、クロージャのコードブロックはそのまま変数に代入することもできます

クロージャはそれが定義された周囲のスコープにある変数を参照することもできます。これは一般的なクロージャとは少し違うかもしれません。

クロージャはさきほどお話ししたとおり、波括弧で囲まれたコードブロックです。ですので、次のように記述します。

{ "こんにちは" }

これはクロージャです。引数をとるには、いくつか方法があります。もしも単一の引数であれば、自動的に「it」という変数に引数の内容がセットされます。ですから次のようなことができます。

{ "${it}さん、こんにちは" }

これでクロージャに名前などを引数で渡すと実行時に「~さん、こんにちは」という文字列を返します。

もちろん明示的に書くことができます。

{name -> "${name}さん、こんにちは"}

コードブロックですので改行を含んでも構いません。


次は引数に型をつけた場合です。

{ String name, int age ->
 "${name}さんは${age}歳です。"
}

もしもクロージャが1つも引数を受け取りたくないという場合があったらどうしましょう?そういうときは矢印だけにします。

{ ->
 "引数なんて、渡さないで!!"
}

これで引数を受け取ることができません。渡すとエラーになります。


代入と実行

Groovyにおいてクロージャは「groovy.lang.Closure」クラスのオブジェクトです。ですから変数へコードブロックを代入することができるのです。では、実行するにはどうしたらいいのでしょうか?

def msg = {-> "こんにちは"}
msg()

上記のコードは実行すると「こんにちは」という文字列を返します。クロージャを代入した変数名に丸括弧をつけると実行することができます

ただ、これは短縮形ですので、クロージャを代入した変数の「call」メソッドを呼び出すと同じ結果を得ることができます。

def msg = {-> "こんにちは"}
msg.call()

実行するときに引数を渡すことができますが、引数が指定されなかった場合のためにデフォルトの値を指定しておくこともできます。

def greet = { String name, String msg = "こんにちは"
 "${name}さん${msg}"
}

greet("m0t0k1") // m0t0k1さんこんにちは

greet("nalulabo", "おはよう") // nalulaboさんおはよう

さらに、メソッドと同様に可変引数を使用することもできます。

def greet = { String name, String... args
 "${name}さん${args}"
}


クロージャにおけるthisとowner、そしてデリゲート

最近のプログラミング言語ではデリゲートという考え方を用いたプログラミングをすることができます。そのためにはGroovyにおける「this」「owner」「delegate」についてそれぞれどう違うのかみておきましょう。

まずthisですが、これはクロージャを定義したクラスを指します。クラス内で作成されたクロージャ内で「this」を参照すると、クロージャの外側のクラスになります。ということは、内部クラスの中で作成されたクロージャ内で参照すると内部クラスのほうを指すことになります。

つぎにownerですが、これはクロージャを定義したオブジェクトを指します。ということは、クラスのほかにクロージャも含まれます。クロージャ内で作成されたクロージャの中から参照したownerは外側のクロージャを表すことになります。

最後にdelegateですが、これの指し示す先はクロージャが実行されるときに決まります。つまり実行時にどのオブジェクトを指しているかを解決したうえでプロパティやメソッドが実行されることになります。

次のコードをみてください。

class Robot {
 private String name = "ワタシハロボット"
 def hello = {

  def greet = {

   this.name

  }

  greet()

 }

}

def r = new Robot()

println r.hello()

クロージャが入れ子になっていますが、thisの指すものはRobotクラスであり、greetというクロージャが返しているのはRobotクラスのフィールドである「name」です。このようにthisはクロージャが作成されたクラスを指します。


delegateを使うと次のようにどこを指すかは自分で指定することができます。

class Robot {
 String name
}

def r = new Robot(name: "なるーらぼ")

def d = { println "${name}です" }

d.delegate = r

d()


上記の例でクロージャが実行されるまで、プロパティに「name」というものが存在しているかどうかはわかりません。しかしdelegateプロパティによりどのオブジェクトに任せるかを決めることができるので、Groovyは任せられたオブジェクト内から「name」プロパティを探し出します

見つかったらそれが利用され、見つからなかった場合は例外が発生するというようになっています。

ただ、こうしたメソッドやプロパティには探す優先順位があると思いませんか?デフォルトではクロージャのownerが先に検索対象になります。ここではスクリプトとしてクロージャが作成されているので、そこに「name」が存在しない場合はdelegateプロパティで与えられたオブジェクト内を検索するようになります。

もちろん、この優先順位は変更することができます。探し方は次のとおりです。

  1. オーナーが先、デリゲート対象が後
  2. デリゲート対象が先、オーナーが後
  3. オーナーオブジェクトのみ
  4. デリゲート対象オブジェクトのみ
  5. 自分で準備した方法

いずれもクロージャのもつ「resolveStrategy」プロパティへ上記の5つのいずれかを渡すことで方針を変更することができます。


GString内のクロージャ

GStringを使うと変数展開できましたよね。では以下のような場合、どうなるでしょうか?

def msg = "おはようございます。"
def greeting = "なるーらぼさん、$msg"
println greeting

msg = "こんにちは"

println greeting

これ、実行すると同じ結果が2回表示されます。なぜでしょうか?これは変数展開されたときに文字列としては確定してしまうからです。ですから、あとで変数の内容を書き換えても結果は変わりません。

もしもこれを参照時に更新してほしい!というニーズがある場合はクロージャを使います。それもGString内で、です。書き直すと次のようになります。

def msg = "おはようございます。"
def greeting = "なるーらぼさん、${ -> msg}"
println greeting

msg = "こんにちは"

println greeting

引数をとらないクロージャで記述しました。こうすると思ったとおりの結果を得ることができたのではないでしょうか?


メソッドポインタ演算子

この演算子を使うと、メソッドへの参照をクロージャに変換することができます。

class Robot {
 String speech() {
  "hello."

 }

}

def r = new Robot()

def greet = r.&speech

println greet()

この演算子はクロージャを返します。クロージャになる、ということはメソッドやクロージャなど別の機能が引数にクロージャをとる場合に利用することができる、ということでもあります。


最後に

今回でGroovyの基本的な要素については一通りお話ししたことになります。

まだこれだけでは具体的にどんなものをプログラミングすることができるかについてピンと来ないかもしれません。

ですがここまでの内容を少しずつGroovyスクリプトとして書いてみて、親しんでいただければと思います。

別のシリーズではファイル入出力なども扱っていきたいと思います!

もんが(なるーらぼ)

個人でプログラミングの学習サイト「なるーらぼ」を運営しています。
https://nalu-labo.amebaownd.com
PowerShell入門の電子書籍2冊も出版しています。
http://www.amazon.co.jp/dp/B017LJOCJ2

なるーらぼ

ごく自然にプログラミングを楽しむことができる世界をつくるお手伝いをしています

0コメント

  • 1000 / 1000