JAva 8のlambda式(変数作用ドメイン)

3284 ワード

通常、lambda式の閉じたメソッドまたはクラスに他の変数にアクセスすることを望んでいます.たとえば、次のようにします.
package java8test;

public class T1 {
    public static void main(String[] args) {
        repeatMessage("Hello", 20);
    }
    public static void repeatMessage(String text,int count){
        Runnable r = () -> {
            for(int i = 0; i < count; i++){
                System.out.println(text);
                Thread.yield();
            }
        };
        new Thread(r).start();
    }
}

lambda式の変数countとtextを参照してください.lambda式では定義されていませんが、メソッドrepeatMessageのパラメータ変数です.考えてみると、ここに隠れたものがあることに気づきます.Lambda式はrepeatMessageが戻ってから実行される可能性がありますが、パラメータ変数は消えています.textとcount変数を保持したらどうなりますか?
この点を理解するためには,lambda式をより深く理解する必要がある.lambda式には、次の3つのセクションがあります.
  • コード
  • パラメータ
  • 自由変数の値.ここで、「自由」とは、パラメータではなく、コードに定義されていない変数を指す.

  • 我々の例ではlambda式には2つの自由変数,text,countがある.データ構造は、lambda式がこの2つの変数の値、すなわち「Hello」および20を格納しなければならないことを示す.これらの値はlambda式によって取得されたと言えます(これは技術的に実装された詳細です.たとえば、lambda式を1つの方法しか含まれていないオブジェクトに変換することで、自由変数の値がオブジェクトのインスタンス変数にコピーされます).
    注意:自由変数を含むコードブロックは「クローズドパッケージ(closure)」と呼ばれます.Javaではlambda式が閉パッケージです.実際、内部クラスはずっと閉鎖されています.Java 8では、閉パッケージにより魅力的な文法が付与されています.
    ご覧のように、lambda式は、閉じた役割ドメインの変数値をキャプチャします.Javaでは、取得された値が良好に定義されていることを確認するために、重要な制約を遵守する必要があります.Lambda式では、参照される変数の値は変更できません.たとえば、次の式は非合法です.
    public static void repeatMessage(String text,int count){
        Runnable r = () -> {
            while(count > 0){
                count--;        // , 
                System.out.println(text);
                Thread.yield();
             }
         };
         new Thread(r).start();
    }

    この制約をするには理由がある.Lambda式の変数を変更するのはスレッドが安全ではありません.一連の同時タスクがあると仮定すると、各スレッドは共有カウンタを更新します.
    int matches = 0;
    for(Path p : files)
        new Thread(() -> {if(p ) matches++;}).start();    // matches 

    このコードが合法的であれば、非常に悪い結果を引き起こします.自己増加操作matches++は原子操作ではありません.複数のスレッドが同時にこの自己増加操作を実行すると、何が起こるか分かりません.
    コンパイラがすべての同時アクセスエラーをキャプチャすることを期待しないでください.可変制約はローカル変数にのみ作用し、matchesがインスタンス変数または閉じたクラスの静的変数である場合、結果が同じように定義されていなくてもエラーは報告されません.同様に、共有オブジェクトを変更することも完全に合法的であり、それが適切でなくても.例:
    List<Path> matches = new ArrayList<>();
    for(Path p: files)
    // matches , 
        new Thread(() -> {if(p ) matches.add(p);}).start();

    注意matchesは「有効final」です(有効なfinal変数が初期化されると、新しい値の変数は永遠に与えられません).我々の例では、matchesは常に同じArrayListオブジェクトを参照するが、このオブジェクトは可変であるため、スレッドは安全ではない.複数のスレッドがaddメソッドを同時に呼び出すと、結果は予測できません.
    Lambda式のメソッドはネストされたコードブロックと同じ役割ドメインを持つ.したがって、同じ名前の競合およびシールドルールも適用されます.lambda式では、ローカル変数と同じ名前のパラメータまたはローカル変数を宣言することはできません.
    Path first = Paths.get("/usr/bin");
    Comparator<String> comp = (first,second) ->
        Integer.compare(first.length(),second.length());
    // , first 

    1つの方法では、同じ名前のローカル変数を2つ持つことはできません.そのため、lambda式にこのような変数を導入することはできません.
    lambda式でthisキーワードを使用すると、lambda式を作成する方法のthisパラメータを参照します.次のコードを例に挙げます.
    public class Application{
        public void doWork(){
            Runnable runner = () -> {....;System.out.println(this.toString());......};
        }
    }

    式this.toString()は、RunnableインスタンスのtoString()メソッドではなく、ApplicationオブジェクトのtoString()メソッドを呼び出します.lambda式でthisを使用するのは、他の場所でthisを使用するのと何の違いもありません.Lambda式の役割ドメインはdoWork()メソッドにネストされ,thisがメソッドのどこに位置しても意味は同じである.