Kotlinの@[アットマーク]について

  • 11
    いいね
  • 1
    コメント

この投稿は、Kotlin Advent Calendar 2016の22日目の投稿です。

はじめに

私自身はKotlinを2015年の6月頃から触れ始めたのでKotlin歴は約1年半ぐらいになります
昨年のAdventCalendarはこんな記事を書いてました

2016年1月にリリースされたFRESH!というサービスの開発に携わっています
現在はkaptでも問題なく動作しますが、開発当初はまだKotlinがベータ版のM11だったので
annotation processorを使用するdagger(DI用ライブラリ)の部分以外は全てKotlinで書かれています

今回はannotation processorでも使われるKotlinの@の使い方について3つ紹介したいと思います

ちなみに@は日本語だと「アットマーク」と呼びますが、英語では「at sign(at symbol)」と呼びます
和製英語と気付かずに使っている人が多いので英語名も覚えておくと良いですよ

Annotation


annotation class Example(val example: String)

@Example("Hello") class Hoge {
}

一番良く見る表現だと思います
説明不要ですね
Javaでもannotationを@で表すので、Kotlinを知らなくても理解出来ると思います

annotation class Example

val hoge = @Example { println("hello world") }
hoge.invoke()

annotationもlambdaに書けます
Kotlinならではの書き方という感じですね

invoke() を呼ぶとlambdaの中身が呼ばれます

val permission = @NeedsPermission(Manifest.permission.CAMERA) {
    MainActivityPermissionsDispatcher.showCameraWithCheck(this);
}        
view.setOnClickListener { permission.invoke() }

現在のプロジェクトではannotation libraryがdaggerしか入っていないので良い使い方が思いつかないのですが、hotchemiさんのPermissionsDispatcherとかこんな感じで書けると良いのでしょうか?
もちろん妄想なので動きません
なんか画期的な方法があったら教えて頂けると嬉しいです!

Kotlinの公式ドキュメントではquasarというlibraryのSuspendable annotationの例があります

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) }

threadのライブラリなので、確かに相性は良さそうです

label

fun func(): Boolean {
      (0..10).forEachIndexed { index, item ->
          println(index)
          return true //①
      }
      return false
}

for文の中でreturnをする場合①でreturnをした場合当然ながら
0がprintされて、この関数の戻り値は必ずtrueになります

それでは、indexが5以下の時に5までprintしてtrueを返したい場合はどうでしょうか?
Javaに慣れているとcontinueやbreakが思い浮かぶと思いますが、
これらはfor文等のloop文でのみしか使えないためforEachIndexed内では使えません
ちなみにforEachはIterable<T>の拡張関数になってるので、実体はただの関数です

fun func(): Boolean {
        (0..10).forEachIndexed { index, item ->
            println(index)
            if (index < 5) return@forEachIndexed
            return true
        }
        return false
}

returnの後に@をつけて関数のlabelを付けると関数の先頭に戻ります
Javaでいうcontinueに似た動作をします

Observable.fromCallable { 1 }
          .subscribe {
              if(it == 1) return@subscribe         
              println("not called here")
          }

みんな大好きRxのsubscribeも同じように@subscribeを付けてreturn出来ます

それでは、lamda式のような無名関数の場合はどうでしょう

val hoge = 5
val lamda = label@ {
    println("printed here")
    if(hoge > 3) return@label
    println("not printed here")
}
lamda.invoke()

無名関数の場合は@を使用して自分で名前をつけてあげることで上記と同様の動作になります
Kotlinを使っているとlamda式を使うことが必然的に増えますので、このような書き方を結構します

内部クラスからの外部クラス参照

最後に内部クラスから外部クラス参照のやり方を見ていきます
上の2つは初心者向けの内容ですが、Kotlinに慣れている方でもこの書き方することが少ない無いので意外に知らない方もいるのではないでしょうか
とは言っても基本的な原理は2番目のlamdaで解説した内容と同じです

Javaでは内部クラスから親クラスのFoo自体を参照する場合以下のような書き方をします

public class Foo implements View.OnClickListener {

    private Button button;

    public Foo() {

    }

    void hoge() {
        Handler handler = new Handler();
        handler.post(new Runnable(){
            public void run() {
                button.setOnClickListener(Foo.this);
            }});
    }

    @Override
    public void onClick(View view) {

    }
}

これをKotlinで書くとこうなります

class Foo : View.OnClickListener {
    private val button: Button? = null

    fun hoge() {
        val handler = Handler()
        handler.post { mButton?.setOnClickListener(this) }
    }

    override fun onClick(view: View) {

    }
}

まぁ当然といえば当然でしょう
それではKotlinのrunのような関数内で呼ぶ時はどうなるでしょうか

class Foo : View.OnClickListener {
    private val button: Button? = null

    fun hoge() {
        button?.run {
            setOnClickListener(this) // compile error
            setOnClickListener(this@Foo) // works
        }
    }
}

run内のthisはrunを指します
そのため、Fooクラスのclick listenerにbindする場合はthis@{クラス名}で呼び出せます

これを応用すると、lambda関数内から親クラスのメソッドにアクセスすることも出来ます
例えば、ParentクラスとOtherクラスがあってChildクラス内でOtherクラスの変数を持っていて、
ParentとOtherに同じ名前のメソッドがあります
同じ名前があるとChild内のOtherからsameNameFunc()を呼び出すと当然OtherのsameNameFunc()が呼ばれます
ちなみに、{}を使わずに=の書き方をするとthis@Child無しではコンパイルエラーになります
=> 無限loopになる為
上記を応用して@Childでこのクラスのメソッドを指定することで、
親クラスのメソッドを呼び出せます


class Child : Parent() {

    val other = object : Other() {
        override fun sameNameFunc() = this@Child.sameNameFunc() // parent method
    }
}

open class Parent() {
    open fun sameNameFunc() = "Parent"
}

open class Other {
    open fun sameNameFunc() = "Other"
}

おわりに

Kotlin色んな書き方があって面白いですね
明日はdigitaljunkyさんのKotlin + Spring Bootです
お楽しみに