その場で無名クラスを定義するのがオブジェクト式と呼ばれる構文です。今回はオブジェクト式を使った無名クラスの定義方法や使い方をお伝えしたいと思います。
オブジェクト式の定義方法
fun main(){
val x = object{
val num = 1
fun run() = "スタート"
}
println(x.num) //1
println(x.run()) //スタート
}
object
の後に波括弧{}
、その中にプロパティやメソッドを定義します。ここで重要なのは、この無名クラスの定義がmain関数内に存在するということです。
あらかじめ宣言を済ませておくオブジェクト宣言とは、
- 使うその場で定義する
- オブジェクト自体に名前が無い
- 式であるため、結果を変数に代入できる
といった点が大きく異なります。
インターフェイスを継承する
オブジェクト式はクラスや、1つ以上のインターフェイスを継承できます。その中でも使用頻度が高いのは、インターフェイスを実装した無名クラスです。
//インターフェイスの定義
interface RunnableInterface{
fun run(): String
}
fun main(){
val x = object: RunnableInterface{
//インターフェイスのメソッドをオーバーライド
override fun run(): String {
return "スタート"
}
}
println(x.run()) //スタート
}
記述方法は通常のクラスと変わりません。
関数のパラメータとして利用する
さらにこの無名クラスは関数のパラメータ(引数)として、関数に渡すことができます。
「あるインターフェイスを実装した無名クラス」を引数に取る関数を実行する処理は、Androidなどでは頻繁に行われますが、慣れないとなかなか把握しにくいので注意しましょう。
前提として、「あるインターフェイス」と、「その型を引数に取る関数」があります。
//インターフェイスの定義
interface RunnableInterface{
fun run(): String
}
//関数定義
//インターフェイスを実装したオブジェクトを引数に取る
fun runMethod(o: RunnableInterface){
println(o.run())
}
そしてmain関数内。関数呼び出しの引数には、オブジェクト式が確認できます。
fun main(){
//runMethod関数の引数は、インターフェイスを継承したオブジェクト
runMethod(object: RunnableInterface{
override fun run(): String {
return "スタート"
}
})
}
//スタート
setOnClickListener
これを実際に使用した例が、AndroidのsetOnClickListenerメソッドです。
button.setOnClickListener( //setOnClickListenerメソッドは、
//View.onClickListenerインターフェイスを継承した、無名クラスを引数に取っている
object : View.onClickListener {
//インターフェイスのメソッドをオーバーライド
override fun onClick(v: View) {
textView.text = "タップされたボタン"
}
}
)
実際のコードではSAM変換によって、
button.setOnClickListener{textView.text = "タップされたボタン"}
まで短くすることができますが、実際にはこのようにオブジェクト式によって、無名クラスを引数として渡しています。
オブジェクト式をネストする
型が無い問題
オブジェクト式は他の構文と同じようにクラスにネストすることができますが、次のような使い方をするには注意が必要です。
- プロパティとして、直接オブジェクト式を設定する
- メソッドの戻り値として、オブジェクト式を設定する
例えばこのような場合です。
//注: このコードはエラーになります
class Example{
//プロパティにメソッドを持つオブジェクト式を代入
val start = object{
fun run() = "スタートします"
}
//関数の戻り値がオブジェクト式
fun stop(){
return object {val str = "ストップします"}
}
}
オブジェクト式は無名クラスを作り出します。無名であるということはすなわち、これらのプロパティやメソッドには「型」という枠がありません。これが大きな問題になります。
エラーを無くすにはstartプロパティの型、stopメソッドの戻り値の型を、どちらも「Any」とするしかありません。
class Example{
val start: Any = object{
fun run() = "スタートします"
}
fun stop(): Any{
return object {val str = "ストップします"}
}
}
これでエラーは抑制されますが、今度はAnyという型との整合性が問題になります。
Anyにはもちろんstartプロパティなど存在しません。これ以上は蛇足ですが、端的に言えばものすごく面倒な状況になってしまいます。
型が無い問題の解決法
この「型が無い問題」を解決するにはどうすればよいでしょうか? Anyに対してなんちゃらかんちゃらするのは、どう考えても効率が悪すぎます。
そこで出てくるのが、またもインターフェイスです。「型」という概念を提供するインターフェイスによって、この問題は解決できます。
//無名クラスで使うプロパティ、メソッドをインターフェイスのメンバとする
interface Start{
val str: String
fun run(): String
}
class Example{
//無名クラスにインターフェイスを継承させる
val start: Start = object: Start{
override fun run() = "スタートします"
override val str = ""
}
//戻り値の型にインターフェイスを指定
fun stop(): Start{
return object: Start {
override fun run() = ""
override val str = "ストップします"}
}
}
fun main(){
val x = Example()
println(x.start.run()) //スタートします
println(x.stop().str) //ストップします
}
無名クラスはインターフェイスと組み合わせることで、より便利に様々な場所で活用できるようになります。