Help us understand the problem. What is going on with this article?

【小ネタ】Kotlinでnullableな関数型を定義するときの '?.invoke()' について

高階関数やラムダ式でnullableの関数型を渡したい場合があります。

..と文章にすると複雑ですが、Buttonで onClick()コールバックを渡すようないつもの処理の場合です。
nonnullで onClick 定義する場合は以下のように onClick()を呼ぶと実行できます。

fun nonnullFunction(onClick: () -> Unit) {
    onClick()
}

nullableの式を実行する場合、 onClick() とするとコンパイルエラーとなります。
エラー時のKotlin Playgroundのリンクを以下に貼ります。
https://pl.kotl.in/b3Y5BDPzs?theme=darcula

fun nullableFunction(onClick: (() -> Unit)?) {
    onClick() //コンパイルエラー
}

エラー内容は以下の通りです。?.invoke() を使用するように指示されます。

Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead.

指定通り以下のようにすると正しく実行できます。

fun nullableFunction(onClick: (() -> Unit)?) {
    onClick?.invoke()
}

invoke()とは

nullableの際に突然出てきた invoke() が気になり調べました。

下記のページから、
最初のonClick() はシンタックスシュガーで onClick.invoke() をオーバーロードしていることがわかります。

Higher-Order Functions and Lambdas
https://kotlinlang.org/docs/reference/lambdas.html

Operator overloading > Invoke operator
https://kotlinlang.org/docs/reference/operator-overloading.html#invoke

すなわち以下は onClick() を呼んだときの処理と同じ挙動です。

fun nonnullFunction(onClick: () -> Unit) {
    onClick.invoke() // equals onClick()
}

kotlin.jvm.functionsのソースコードを見ると invoke() のインターフェイス定義を見ることができます。
Kotlin1.3では引数定義は0から22まで可能なことがわかります。

package kotlin.jvm.functions

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}

:

/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

invoke() の処理を変更

invoke() をオーバーロードすることにより、関数型を実行するときの処理を変更可能です。

https://stackoverflow.com/questions/45173677/invoke-operator-operator-overloading-in-kotlin

上記Stackoverflowの例がわかりやすかったので参考にしました。
サンプルコードは以下となります。
関数型と invoke() との関係を知った後だと理解しやすいと思います。

class Greeter(val greeting: String) {
    operator fun invoke(target: String) = println("$greeting $target!")
}

fun main() {
    val hello = Greeter("Hello")
    hello("world")  // Prints "Hello world!"
}

Kotlin Playgroundのリンクを以下に貼ります。
https://pl.kotl.in/Q0pi-t1Am?theme=darcula

感想

nullableな関数型の実行には ?.invoke() を使用します。
invoke()を調べることによってすこしKotlinの理解を深めた気がします。
ブラウザで手軽に試せるPlayground便利です。

関連

https://try.kotlinlang.org/#/Kotlin%20Koans/Conventions/Invoke/Task.kt

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away