Kotlin:クロージャ(Closure)

投稿日:  更新日:

クロージャ(Closure)は新しい概念ではなく、関数型プログラミングに古くから存在していました。

手続き型やオブジェクト指向プログラミングにも、だいぶ前から採用が進んでいます。

Kotlinもクロージャが使える言語の1つです。

スポンサーリンク

関数のローカル変数のスコープ

関数のローカル変数のスコープ(有効な範囲)は、その関数内です。

従って、関数Aから関数Bのローカル変数は参照できません。

関数のローカス変数のスコープ

しかし、関数内に子関数を定義すると、関数Dから関数Cのローカル変数が参照できます。

関数のローカス変数のスコープ

スポンサーリンク

クロージャとは

以下はクロージャの例です。記述のポイントは2つです。

  (1)子関数から親関数のローカル変数を参照
  (2)親関数が子関数オブジェクトを戻り値で返す

    fun increment(): ()->Int {
        var value = 0
        fun plus1(): Int {
            return value++    // 親関数のローカル変数を参照
        }
        return ::plus1	      // 子関数オブジェクトを戻り値で返す
    }

関数のローカル変数の寿命はその関数内です。関数の処理が終われば破棄されます。

関数のローカル変数の寿命

しかし、「クロージャ―の例」のような構成になっていると、親関数のローカル変数は破棄できません。子関数から参照される可能性があるためです。

関数のローカル変数の寿命

この時、親関数の戻り値で返される子関数オブジェクトは、参照する変数(「実行環境」と表現される)を含んだものになります。

クロージャの構成

このように「関数+参照する変数(実行環境)」が格納された関数オブジェクトをクロージャといいます。

「クロージャの例」の記述中で、子関数が使用されるのは一度だけです。一度だけしか使われない関数へ名前を付ける事は無駄です。

よって、子関数はラムダ式(無名関数)を用いるのが通例です。

    fun increment(): ()->Int {
        var value = 0
        return { value++ } // 子関数オブジェクトを返す(ラムダ式)
    }
スポンサーリンク

クロージャの独立性

作成されたクロージャは各々独立した動作をします。お互いに干渉しません。

また、作成時の環境(参照する変数)を格納したものになります。作成時の環境をキャプチャーしたような動作です。

    fun increment(start: Int = 0): ()->Int {
        var value = start
        return { value++ }
    }

    fun exec() {
        //
        // クロージャはお互いに独立、干渉しない
        //
        val _inc1 = increment()
        val _inc2 = increment()
        println("Inc1 = ${_inc1()}")    // => 0
        println("Inc2 = ${_inc2()}")    // => 0
        println("Inc1 = ${_inc1()}")    // => 1
        println("Inc2 = ${_inc2()}")    // => 1
        println("Inc1 = ${_inc1()}")    // => 2
        println("Inc2 = ${_inc2()}")    // => 2
        //
        // クロージャは作成時の環境を格納する
        //
        val _inc3 = increment(10)   // 初期値10でクロージャ作成
        println("Inc3 = ${_inc3()}")    // => 10
        println("Inc3 = ${_inc3()}")    // => 11
        println("Inc3 = ${_inc3()}")    // => 12
    }
Inc1 = 0
Inc2 = 0
Inc1 = 1
Inc2 = 1
Inc1 = 2
Inc2 = 2
Inc3 = 10
Inc3 = 11
Inc3 = 12
スポンサーリンク

関連記事:

Kotlinは関数を変数に代入したり、引数で受け渡したりできます。 関数を第一級オブジェクトで扱えるためです。 これにより、関数を使った処理の委譲が容易になりました。ArrayOf#forEach( )がその典型的な例です。 以上のような操作で必要となる「関数オブジェクト」について、まとめます。 ...
Androidのアプリを作成しているとSAM変換が頻繁に登場します。 プログラムが簡素に記述できることから、プログラマーに好評なようです。 Kotlinが関数を第一級オブジェクトとして扱える恩恵です。 しかし、私はSAM変換に出合うと、いつもプログラムの論理が把握できなくて戸惑います。なので、苦手です。あまり好きではありません。 苦手を克服するために、もっとSAM変換の理解を深めたいと思い、まとめてみました。 ...
近頃の携帯端末はクワッドコア(プロセッサが4つ)やオクタコア(プロセッサが8つ)が当たり前になりました。 サクサク動作するアプリを作るために、この恩恵を使わなければ損です。 となると、必然的に非同期処理(マルチスレッド)を使うことになります。 JavaのThreadクラス、Android APIのAsyncTaskクラスが代表的な手法です。 Kotlinは上記に加えて「コルーチン(Coroutine)」が使えるようになっています。 今回は、このコルーチンについて、まとめます。 ...
Android Studio Giraffe(2023.07)になって、ビルドスクリプトの推奨がKotlin DSLになりました。 この機会に、ビルドスクリプトと記述言語についてまとめます。 ※環境:Android Studio Giraffe | 2022.3.1    :Android Gradle Plugin 8.1.0    :Gradle 8.0 ...
Kotlinにおいて、各クラスのインスタンスに自動生成されるequals()関数は、classとdata classで比較対象に違いがあります。 紛らさしいので、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10 ...
KotlinはJavaのプリミティブ型に当たるデータ型を持ちません。しかし、同等な記述が基本データ型を用いて出来ます。 Kotlinにおいて、基本データ型がどのように扱われているのか、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10 ...
継承を行うことなく新しい機能(関数やプロパティ)をクラスへ追加する「拡張機能」についてまとめます。 何処にでも手軽に定義できるため、とても重宝する仕組みですが、乱用するとプログラムが無法地帯になるので、管理は重要です。そのために、十分な理解が必要になります。 ちなみに、Android Jetpackは、この「拡張機能」を活用して作られた部分が多くあります。「拡張機能」無くして、成り立たないシステムです。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 1     Kotlin 1.8.10 ...
スコープ関数はKotlinの標準ライブラリで提供されています。 そのことから、Kotlinを特徴付ける重要な構文であることが分かります。 ただし、「プログラムの動作を定義する構文」ではなく、「プログラムのコードの品質を上げる構文」です。 同様な動作はスコープ関数を使わなくても記述できます。しかし、スコープ関数を使えばスマートな(洗練された)記述になります。 積極的に使いたいと思いますが、applyとlet以外はあまり使う機会がありません。また、稀にwithなどが登場すると、コードの流れが理解できずに戸惑います。 ですので、スコープ関数について、まとめました。 ※環境:Android Studio Hedgehog | 2023.1.1 Patch 2     Kotlin 1.9.0     Compose Compiler 1.5.1 ...
スポンサーリンク