GroovyOnAndroidやRxJavaをやりたい人のための、Groovyのクロージャの書き方・使い方メモ

  • 33
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

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のサンプルコードなどでこんなコードが出てきます。

GroovyOnAndroidの例
button.setOnClickListener { Log.v(TAG, "Clicked Button!") }

RxJavaのwikiにこんな感じのコードが載っています。

RxJavaの例
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と表示

 普通は上記のようにクロージャを生成してすぐには使いません。クロージャを引数にとるメソッドと一緒に使います。またコールバックやリスナーを登録するためにも使います。

Groovyのクロージャの利用例
assert [1, 2, 3].collect({Integer num -> return 2 * num}) == [2, 4, 6]

 ちなみに上記の「クロージャの生成と呼び出しの例」と「Grooovyクロージャの利用例の書き方」のような書き方はあまりしません。もっと短いクロージャの書き方があります。

引数の型を省略できる

 次のように引数の型を省略して書くことも可能です。

引数の型を省略したGroovy
Closure clos = { message -> println messagge}

 Groovyにはdefというキーワードがあります。defは変数、メソッドの返り値型、メソッドの引数の型に指定可能です。defを指定した場合、Objectを指定しているのと同じです。

defキーワードで変数を宣言
def message = "Hello"
println message // Helloと表示
defキーワードでメソッドをの返り値型と引数の型を宣言
def get0(){
    return 0
}

void printMessage(def message){
    println message
}

 また、メソッドの引数の場合は、defすら省略することが可能です。

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がある

 こんな書き方ができます。

itを使う
Closure clos = { println it }
clos("Hello Closure with it") // "Hello Closure with it と表示

 クロージャの基本的な書き方は次のようなものでしたね。上記の書き方では->がありませんね。

基本的なクロージャの書き方
{ 引数のリスト -> 処理 }

 ->を省略した場合、暗黙の引数itがあると見なされます。そのため、

引数名を明示するものと、itを使うものの比較
Closure printMessage = { message -> println message }
printMessage("Hello")

Closure printMessageWithIt = { println it }
printMessageWithIt("Hello")

上記のように引数が1個のクロージャは、明示的に引数を書くこともできますが、暗黙の引数itを使って簡潔に記述することも可能です。

気をつけないと行けないのは、

暗黙の引数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インターフェースの例を示します。

asを使ってクロージャを強制型変換
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インターフェースとして渡すことができます。

setOnClickListenerでas+クロージャを使う
button.setOnClickListener({ Log.v(TAG, "On Clicked!") } as View.OnClickListener)

 以下は、Javaで記述した場合です。

setOnClickListenerを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を書く必要はありません。

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!") }
setOnClickListenerで強制型変換+クロージャを使う
button.setOnClickListener({ Log.v(TAG, "On Clicked!") })

 簡潔になりましたね。

Groovyは()を省略できる場合がある

 Groovyはメソッド呼び出しの()を省略できる場合があります。
メソッドの引数が1個以上で、かつトップレベルにある式のメソッド呼び出しの場合です。

 そのため次のように記述することが可能です。

setOnClickListenerで強制型変換+クロージャを使う
// 括弧省略前
// 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で書いた例。

setOnClickListenerとView.OnClickListener
button.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        Log.v(TAG, "On Clicked!");
    }
});

 GroovyOnAndroidの例です。

setOnClickListenerとView.OnClickListenerとクロージャ
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以上を使っていることが前提です。

RxJavaでクロージャ
@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のあのコード、こういう風によめるのか」という方がいたら嬉しいです。

 間違いなどありましたら、指摘していただけると嬉しいです。

ついでに、よかったらどうぞ!