この投稿は、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です
お楽しみに