はじめに
「GroovyがAndroid上で実行可能に」
とか、
「Groovy and Android: a winning pair by Cédric Champeau」
とか、
「Groovy On Android」
を見て、GroovyでAndroidを書いてみたくなりました!Groovyにより記述されたパワフルなAndroid向けライブラリもたくさん出てくるでしょう。Groovyにより冗長だった記述がより簡潔になると思います。Groovy 2.4の正式リリースが待ち遠しいです。(投稿執筆時は、Groovy2.4-rc3が最新)
また、
「関数型言語を学ぶことは実務でどう役に立ったか」
とか、
「iOSのSwiftとAndroidのGroovy」
を読んで、RxJavaにも興味が出てきました。ただRxJavaをJava 6やJava 7でやる場合、コールバックを記述する箇所がちょっと冗長になりそうですね。Java 8でやるぶんにはラムダ式があるから良いのでしょうが。RxJavaをAndroidで使うならば、Groovyと合わせて使うとより簡潔に読みやすく記述できそうですね。(Java 7などでラムダ式を使えるRetroLambdaというのもあるようですが...)
さて、GroovyOnAndroidのサンプルコードなどでこんなコードが出てきます。
button.setOnClickListener { Log.v(TAG, "Clicked Button!") }
RxJavaのwikiにこんな感じのコードが載っています。
Observable.from("Taro", "Jiro", "Saburo").subscribe{
println "Hello " + it + "!"
}
Groovyになじみの無い方は、「なんだコレ?」とか、「あれView.OnClickListenerじゃないの?」とか思ったかもしれませんね。実はこれらのGroovyのコードでは、クロージャを使っているのです。
この投稿ではGroovyのクロージャの書き方、読み方、使い方を一部簡単ではありますが紹介します。GroovyOnAndroidやRxJavaのサンプルやwikiを見る際の助けになれば幸いです。
興味がわいた方は、プログラミングGROOVYなどの分かりやすい本や、Groovy詳しい方のブログで勉強するのが良いと思います。
Groovyにはクロージャがある
Groovyにはクロージャがあります。Groovy公式ページ(日本語約)の「クロージャ」によると
Groovyのクロージャは「コードブロック」やメソッドポインタのようなものです。ひと固まりのコードとして定義され、後になって実行されます。
とのことです。
クロージャの基本的な書き方は、
{ 引数のリスト -> 処理 }
です。
いくつか例を示します。
// println はSystem.out.printlnのショートカット
// Groovyではセミコロンを省略可
// シンプルなクロージャ
Closure clos = { println "Hello Closure" } // Closureインスタンスの生成
clos.call() // クロージャの呼び出し Hello Closureと表示
clos() // 普通はこっちの書き方で呼び出す
// 引数のあるクロージャ
Closure closWithArg = { String message -> println message }
closWithArg("Hello Closure") // Hello Closureと表示
// 返り値のあるクロージャ
Closure closReturnObject = { -> return "Hello Closure" }
println closReturnObject() // Hello Closureと表示
// 返り値と引数のあるクロージャ
Closure closWithArgReturnObject = { String message -> return message }
println closWithArgReturnObject("Hello Closure") // Hello Closureと表示
普通は上記のようにクロージャを生成してすぐには使いません。クロージャを引数にとるメソッドと一緒に使います。またコールバックやリスナーを登録するためにも使います。
assert [1, 2, 3].collect({Integer num -> return 2 * num}) == [2, 4, 6]
ちなみに上記の「クロージャの生成と呼び出しの例」と「Grooovyクロージャの利用例の書き方」のような書き方はあまりしません。もっと短いクロージャの書き方があります。
引数の型を省略できる
次のように引数の型を省略して書くことも可能です。
Closure clos = { message -> println messagge}
Groovyにはdefというキーワードがあります。defは変数、メソッドの返り値型、メソッドの引数の型に指定可能です。defを指定した場合、Objectを指定しているのと同じです。
def message = "Hello"
println message // Helloと表示
def get0(){
return 0
}
void printMessage(def message){
println message
}
また、メソッドの引数の場合は、defすら省略することが可能です。
def printMessage(message){
println message
}
クロージャの引数でも同じように、任意(オプショナル)に型を付けた書き方もできますし、defで指定することもできますし、defも省略し型を書かない書き方もできます。
// 型を付ける
Closure clos0 = { String message, String name -> println "${message} ${name}" }
clos0 ("Hello", "Ryota")
// defで指定
Closure clos1 = { def message, def name -> println "${message} ${name}" }
clos1 ("Hello", "Ryota")
// defも省略し型を書かない
Closure clos2 = { message, name -> println "${message} ${name}" }
clos2 ("Hello", "Ryota")
ちなみに、Groovy2.0から静的型チェックと静的コンパイルが追加されました。プログラミングGROOVY別冊:第8章 Groovy 2.0の新機能での解説がとても分かりやすいです。
returnも省略できる
Groovyではクロージャやメソッドで、次のようにreturnが省略可能な場合があります。
Closure clos0 = { Integer num -> return num * 2 }
println clos0(10) // 20と表示
Closure clos1 = { Integer num -> num * 2 }
println clos1(10) // 20と表示
Closure clos2 = { Integer num ->
if(num > 0) {
num * 2
} else {
0
}
}
println clos2(1) // 2と表示
println clos2(0) // 0と表示
println clos2(-1) // 0と表示
Closure clos3 = { ->
println "Hello! "
0
}
def num = clos3() // Hello!と表示
println num // 0と表示
Java8のラムダ式やC#のラムダ式では、ラムダ式の処理の文が一文の場合のみreturnを省略できますが、Groovyのクロージャでは文が複数あってもreturnを省略することが可能です。
暗黙の引数itがある
こんな書き方ができます。
Closure clos = { println it }
clos("Hello Closure with it") // "Hello Closure with it と表示
クロージャの基本的な書き方は次のようなものでしたね。上記の書き方では->
がありませんね。
{ 引数のリスト -> 処理 }
->
を省略した場合、暗黙の引数itがあると見なされます。そのため、
Closure printMessage = { message -> println message }
printMessage("Hello")
Closure printMessageWithIt = { println it }
printMessageWithIt("Hello")
上記のように引数が1個のクロージャは、明示的に引数を書くこともできますが、暗黙の引数itを使って簡潔に記述することも可能です。
気をつけないと行けないのは、
Closure closWithIt = { println "Hello" }
closWithIt(null)
このclosWithItと
Closure closWithNoArgs = { -> println "Hello" }
closWithNoArgs(null) // 実行時例外が発生する
このclosWithNoArgsは違うということです。closWithItは暗黙の引数itがあり引数を1個取るクロージャ、closWithNoArgsは引数を取らないクロージャです。
asでSAMインターフェースに変換できる
ここまでずっとGroovyのクロージャの書き方に関してでしたが、やっとここからAndroidとかRxJavaとかが絡んできます。
Groovyのクロージャをasを使って、メソッドが一つだけ定義されたインターフェースに強制型変換することが可能です。AndroidのView.OnClickListenerインターフェースの例を示します。
View.OnClickListener listener0 = { View view -> Log.v(TAG, "On Clicked!") } as View.OnClickListener
View.OnClickListener listener1 = { view -> Log.v(TAG, "On Clicked!") } as View.OnClickListener
View.OnClickListener listener2 = { Log.v(TAG, "On Clicked!") } as View.OnClickListener
もちろん直接、setOnClickListenerのメソッドにView.OnClickListenerインターフェースとして渡すことができます。
button.setOnClickListener({ Log.v(TAG, "On Clicked!") } as View.OnClickListener)
以下は、Javaで記述した場合です。
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.v(TAG, "On Clicked!");
}
});
冗長な部分が多いですね。Groovyでクロージャを使って記述した場合、冗長な部分を削ることが可能で、非常に読みやすくなりますね。
ちなみにまだ短くすることが可能です。
Groovy2.2から暗黙的クロージャ強制型変換がある
Groovy2.2から暗黙的クロージャ強制型変換という機能が追加されました。(Groovy2.2のリリースノート)
前の節ではasを書いていましたが、Groovy2.2からasを書く必要はありません。
View.OnClickListener listener0 = { View view -> Log.v(TAG, "On Clicked!") }
View.OnClickListener listener1 = { view -> Log.v(TAG, "On Clicked!") }
View.OnClickListener listener2 = { Log.v(TAG, "On Clicked!") }
button.setOnClickListener({ Log.v(TAG, "On Clicked!") })
簡潔になりましたね。
Groovyは()を省略できる場合がある
Groovyはメソッド呼び出しの()
を省略できる場合があります。
メソッドの引数が1個以上で、かつトップレベルにある式のメソッド呼び出しの場合です。
そのため次のように記述することが可能です。
// 括弧省略前
// button.setOnClickListener({ Log.v(TAG, "On Clicked!") })
button.setOnClickListener{ Log.v(TAG, "On Clicked!") }
またGroovy 1.8から、メソッドチェイン関連で省略できる場合が増えたようです。
最後の引数がクロージャの場合こんな書き方もできる
最後の引数がクロージャの場合、次のような書き方もできます。
def func(Integer num, Closure clos) {
if(num > 0) {
clos(num)
} else {
clos(0)
}
}
// 普通の書き方
func(10, { println it })
// 最後の引数がクロージャの場合、()の外にクロージャを書ける
func(10){println it}
// インデントを変えれば制御構造のように
func(10){
println it
}
クロージャの書き方いろいろ。GroovyOnAndroidとRxJavaを例に
クロージャの書き方の例をいろいろ示します。
GroovyOnAndroidで、View.OnClickListenerとView#setOnClickListener
まずはJavaで書いた例。
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.v(TAG, "On Clicked!");
}
});
GroovyOnAndroidの例です。
button.setOnClickListener({View view -> Log.v(TAG, "On Clicked!")} as View.OnClickListener)
button.setOnClickListener({View view -> Log.v(TAG, "On Clicked!")}) // asを省略し、暗黙的クロージャの強制型変換
button.setOnClickListener{View view -> Log.v(TAG, "On Clicked!")} // () を省略
button.setOnClickListener{view -> Log.v(TAG, "On Clicked!")} // 引数の型を省略
button.setOnClickListener{ Log.v(TAG, "On Clicked!")} // 暗黙の引数itを使う(実際にitは使っていない)
button.setOnClickListener({ Log.v(TAG, "On Clicked!")}) // もちろんこういう書き方もできる
RxJavaのAction1<T>とObservable<T>#subscribe
RxGroovyを使っているか、Groovy 2.2以上を使っていることが前提です。
@Grab(group='com.netflix.rxjava', module='rxjava-core', version='0.20.4')
import rx.Observable
import rx.functions.Action1
Observable.from("Taro", "Jiro", "Saburo").subscribe(new Action1<String>() {
@Override
public void call(String string) {
System.out.println("Hello " + string + "!");
}
});
Observable.from("Taro", "Jiro", "Saburo").subscribe({ String name -> println "Hello $name"} as Action1)
Observable.from("Taro", "Jiro", "Saburo").subscribe({ String name -> println "Hello $name"})
Observable.from("Taro", "Jiro", "Saburo").subscribe({ name -> println "Hello $name"})
Observable.from("Taro", "Jiro", "Saburo").subscribe({ println "Hello $it"})
Observable.from("Taro", "Jiro", "Saburo").subscribe { println "Hello $it"}
【おまけ】RxGroovyがやっていること
Groovyは2.2から暗黙的クロージャの強制型変換が導入され、Javaのメソッドが一つのインターフェース(SAMインターフェース)を引数にとるメソッドで、変わりにクロージャを記述することができるようになりました。
RxGroovyは、RxJavaを利用する際いくつかのSAMインターフェースを引数にとるメソッドで変わりにクロージャを引数に渡せるようにしたものです。これにより、Groovy 2.0とGroovy 2.1でも、クロージャを用いた簡潔な記述が可能です。
それについて興味がある方はこちらで書いたので見てください。「RxJavaをGroovyで使う。そしてRxGroovyがやっていること(ExtensionModule)」
まとめ
「GroovyOnAndroidやってみたい」、「RxJavaのwikiのあのコード、こういう風によめるのか」という方がいたら嬉しいです。
間違いなどありましたら、指摘していただけると嬉しいです。