KotlinシリーズのLamband表現(2)


前の記事では最も基本的なLamban表現について述べましたが、今日の記事ではLamban表現における作用領域の変数へのアクセスを続けています.
Javaの内部クラスアクセス変数
関数内部で匿名内部クラスを使用すると,関数のパラメータと関数内の局所変数を匿名内部クラス内で使用することができる.Lambda表式を使用すると、この関数のパラメータにもアクセスでき、Lamban式の前に定義された変数を使用することができます.
まず、Javaの匿名内部クラスで関数パラメータとローカル変数にアクセスする例を見ます.
public void search() {
    final String str = "xxxx";
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(str);
            }
        }
    }).start();
}
上記の例ではローカル変数strは匿名の内部クラスで使用されるので、final修飾子を追加する必要があります.
もしあなたが使っているJDKがJava 8バージョンであれば、あなたがfinal修饰符を書かなくても間違いがないことが分かります.これはJava 8においてfinal修饰符が必要ではないため、内部クラスでstrの値を変更しようとすると、IDEが匿名の内部クラスで使用する局部変数がfinalであることを教えてくれます.変更されてはいけません.ですから、Java 8でその値を修正しようとしなかったら、final修饰符は省略できます.
なぜJavaでは匿名の内部クラスで使用される変数はfinalタイプでなければなりませんか?ここでは作用領域に関する問題がある.strの多い作用域はsearch()という関数であり、関数の動作が終了するとこの局部変数は消えてしまいますが、私たちの匿名内部クラスの実行タイミングはsearch()関数の実行が完了する前に、search()関数が終了したかもしれません.スレッドクラスが実行されるとこの変数が見つからずエラーが発生します.フィnal修飾子を使うと、Javaはこの変数を内部クラスのメンバー変数としてコピーします.final修飾のため、このコピーした変数は改竄されず、内部クラスと外部クラスの変数が一致するように保証します.
Ktlinは、スコープ内で変数にアクセスします.
Javaの匿名内部クラスにおける変数アクセスの状況と原理について説明しましたが、上の匿名内部クラスをLambada表現に変えても同じことができます.同じ効果があります.次に、KotlinではLambada式を使って作用領域の変数にアクセスするルールを見てみます.
Kotlinでは、Lambada表式の内部でfinalとして宣言することなく外部の変数にアクセスでき、Lambada内部でこれらの変数を変更することができます.次の例を見ます.
fun countThings(datas : List){
    var count = 0
    datas.forEach {
        if (it == 0){
            count++
        }
    }
    print("count = $count")
}
上のコードforEachで伝達されているのはLamband式で、局所変数countについてはfinalとして宣言されていません.また、この変数はLamda式の内部で修正されます.
Javaと比べるとちょっと不思議ですが、Kotlinはどうやってできますか?
分析の前にまず見てみます.Javaに参加すると、匿名の内部クラスで外部変数の値をどのように修正しますか?
public void search() {
    final String[] str = {"xxxx"};
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            str[0] = str[0] + "|" + i;
            System.out.println(str[0]);
        }
    }).start();
}
上のコードのJavaは、匿名の内部クラスに外部の変数を変更できるようにするために、単一の要素の配列を作成し、finalタイプとして宣言します.このように、str配列はfinalであるが、その中の要素は修正可能であり、これによって匿名内部クラスが関数から匿名内部クラスにコピーされる変数は可変ではなく、匿名内部クラスでこの変数値を修正できることを保証する.
Javaは上記の方法を使ってこの問題を解決する以外に、次のような方法があります.
public void search() {
    final Ref ref = new Ref<>("xxxx");
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            ref.value= ref.value + "|" + i;
            System.out.println(ref.value);
        }
    }).start();
}


static class Ref{
    T value;
    public  Ref(T value){
        this.value = value;
    }
}
上のコードでは静的内部クラスを使用しています.静的内部クラスの参照はfinalタイプですが、内部のvalue属性を変更することができます.Lambada式で外部変数を変更する目的に達しました.
実は上のこの方法はKotlinが使っている方法です.Kotlin内部はこのような方法によりLamband内部で外部変数を修正することができます.ただ、私たちがこのような包装器類を明示的に作成する必要はありません.
したがって、専門用語で説明すると、デフォルトでは、局所変数の宣言周期はこの変数を宣言する関数に制限されていますが、Lambada内部で使用されたら、Lamdaに捕獲されたといいます.このとき、この変数を使用したコードは保存され、後で実行されます.final変数が取り込まれると、その値はこの値を使ったLamban表現と一緒に格納されます.非final変数に対して、その値は上記のようにパッケージ内に封入されます.この値は変更されます.同時に、このパッケージ類の参照とLamdaコードと一緒に格納されます.
正直に言うと、この値をLambandの内部で使用しているだけで、修正しない場合は、Lambadaの表式の内部に値をコピーして、Lamdaの内部で使用すると、Kotlinのパッケージクラスが作成されます.また、このパッケージクラスの参照をLamband表式の内部にコピーして、変更しやすいです.
最後に書く
このセクションでは、これはあくまでもポイントですが、Kotlinは、私たちの背後にある細部を隠してくれました.しかし、背後にある原因は明らかにする必要があると思います.