6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DroidKaigi2019のAndroidクイズでQ4の解説

Last updated at Posted at 2019-02-08

DroidKaigi2019で、リクルートさんのブースで出されていた問題が面白かったので、なぜそうなるのか調べてみたので、解説したいと思います。

問題

val x = object: ((Int) -> Unit) -> Unit by {} {} {}

と宣言した時、Xの型は?

  1. Int
  2. Unit
  3. (Int) -> Unit
  4. ((Int) -> Unit) -> Unit

答えは解説の後に書きます、皆さんも一度考えてみてください。

解説

val x = object: ((Int) -> Unit) -> Unit by {} {} {}

では、上記のコードがどうなっているのか解説していきたいと思います。

objectキーワード

まず、objectキーワードですが、以下の様に無名クラスを作成することが出来ます。
この無名クラスはシングルトンになってます。

val a = object: OnClickListener {
    // オブジェクトのブロック クラスと同じ様にメンバー変数やメソッドの定義が出来る
    override fun onClick(v: View) {
      // ...
    }
}
val x = object: ((Int) -> Unit) -> Unit

上記のコードは、((Int) -> Unit) -> Unitを継承した無名クラスを作っているということになります。

この無名クラスは((Int) -> Unit) -> Unitを継承しているのでinvoke(p1: (Int) -> Unit)をオーバーライドする必要があります。

val x = object: ((Int) -> Unit) -> Unit {
    override fun invoke(p1: (Int) -> Unit) {
        // ...
    }
}

問題のコードでは、なぜinvokeメソッドをオーバライドせずに動くのかは、次の委譲(Delegation)で説明します。

byキーワード 委譲(Delegation)

Kotlinの委譲には2種類あり、今回はクラスデリゲーションが使われています。

  • クラスデリゲーション
  • プロパティデリゲーション

クラスデリゲーションとは?

継承しているインターフェイスのメソッドを、同じシグネチャのメソッドを実装しているインスタンスに委譲できる仕組みです。

interface ClickListener {
    fun onClick()
}

object DefaultClickListener : ClickListener {
    override fun onClick() {
        println("DefaultClickListener onClick")
    }
}

object HogeButton: ClickListener by DefaultClickListener

fun main(args: Array<String>) {
    HogeButton.onClick() // "DefaultClickListener onClick"
}

invokeメソッドをオーバライドせずに動いたの理由は、((Int) -> Unit) -> Unitのラムダからinvokeメソッドを委譲していたからです。

あとはobjectのボディを書くとobject式は完成です。

val x = object: ((Int) -> Unit) -> Unit by { /* ((Int) -> Unit) -> Unit型のラムダ */ } { /* objectのボディ */ } {}

では、byの後にある三つ目の{}は、何なのかをみていきましょう

対応する引数として渡されるラムダ式は括弧の外側に置くことができる

byの後にある三つ目の{}の正体ですが、これは object: ((Int) -> Unit) -> Unit by {} {}のメソッド呼び出しです。

object: ((Int) -> Unit) -> Unit by {} {}を実行するにはinvoke(p1: (Int) -> Unit)を呼び出す必要があります。

val o = object: ((Int) -> Unit) -> Unit by {} {}
o.invoke({ i -> /* Unit */ })
o({ i -> /* Unit */ }) // invokeは()に省略可能です
o { i -> /* Unit */ } // 引数の最後のラムダは丸括弧の外に記述でき、それ以外に引数がない場合は丸括弧を省略できます。

このinvokeの省略と、最後のラムダの場合は丸括弧の外で記述できる記法を使い
object: ((Int) -> Unit) -> Unit by {} {}を実行していたということですね。

これで問題の謎は全て解けました。

回答 まとめ

整理してみると以下の様になります。

val x = object: ((Int) -> Unit) -> Unit by { /* ((Int) -> Unit) -> Unit型のラムダ */ } { /* objectのボディ */ } { /*  object: ((Int) -> Unit) -> Unit by {} {} の実行、呼び出し  */ }

byの後にある1つ目の{}を実行しているので、回答は2. Unitになります。

Kotlinでは、様々な省略記法などがあり様々な書き方が出来ます。
今回の問題の様なコードをプロダクトで書くことはないと思いますが、難読なコードが書けてしまうので気をつけて使いましょう。

6
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?