Problem
SAM変換
Kotlinにはinterfaceに1つのみ関数が定義されている場合にのみLambda式として表せるSAM変換というものがあります。
例えばAndroidでボタンをクリックした際に呼ばれるOnClickListenerをJavaで書くとこうなります。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// click
}
});
OnClickListener
はViewクラスのinterfaceにonClick
というメソッドが1つのみ定義されているので
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
Kotlinでは簡潔にこれだけで書きます。
button.setOnClickListener {
// click
}
しかしながら、SAM変換はJavaで定義されたinterfaceのみ対応していてKotlinで書かれているinterfaceは対応していません。
Kotlinは関数リテラルを型に持てるのでプロパティにコールバックを定義してリスナーの形をとることが可能なのですが、型に名前がつかないのでライブラリの様な不特定多数の人が使うものだと、やはり型名を付けたくなります。
プロパティに関数リテラルを定義してコールバックとして用いたやり方を以前このような記事を書いたので、参考にしてみて下さい。
Kotlinでコールバック
Solution
Type Alias
Kotlin1.1からTypeAliasという機能が導入されました。
C++やSwift等の他の言語にも似た機能があるので知っている人も多いと思います。
簡単に説明すると、例えばHogeというIntとStringを引数にとってStringを返すTypeAliasを定義して、
変数hoge
の型としてHogeを指定することが出来ます。
typealias Hoge = (Int, String) -> String
val hoge: Hoge? = { i: Int, str: String -> "hoge : $str : $i" }
Callback (interface)
今まではJavaの様にinterfaceを使って書くとこのようになります。
interface CallBackListener {
fun onHoge(foo: String, bar: Int)
}
// caller
var callback: CallBackListener? = null
callback?.onHoge("foo", 100)
// callee
val callback = object : CallBackListener {
override fun onHoge(foo: String, bar: Int) {
print("$foo : $bar")
}
}
これだと、Javaで書いているのとあまり変わりません…
Callback (関数リテラル)
関数リテラルにして書いてみます。
// caller
var callback: ((foo: String, bar: Int) -> String)? = null
callback?.invoke("foo", 100)
// callee
val callback = { foo: String, bar: Int ->
print("$str : $i")
}
この形でも、Kotlinっぽく書けなくはありません。
ただ上記でも述べたように、型では無いので引数の型さえ同じなら任意の値を入れることが出来ます。
Callback (TypeAlias)
上2つをTypeAliasで書き直すとこのようになります。
イメージとしてはinterfaceと関数リテラルの良いとこ取りをした感じです。
typealias CallBackListener = (foo: String, bar: Int) -> Unit
// caller
var callback: CallBackListener? = null
callback?.invoke("foo", 100)
// callee
val callback = { foo, bar ->
print("$foo : $bar")
}
TypeAliasを使うとコールバックに型名を付ける事が出来るので、型安全かつKotlinっぽく書くことが出来ました。
2017年1月現在はまだKotlin1.1はbeta版ですが、近いうちに正式リリースされる予定なので是非使ってみて下さい。
※ 指摘がありましたので、2017/1/24にタイトル、内容を一部変更致しました。