Java 8新機能:Lambda式の役割ドメイン(Lambda式補完版)


Lambda式の役割ドメイン
文|莫若吻
Lambda式では、外部の役割ドメインと古いバージョンの匿名オブジェクトにアクセスする方法が似ています.finalがマークされた外層ローカル変数、またはインスタンスのフィールド、および静的変数に直接アクセスできます.
Lambda式はスーパークラス(supertype)から変数名を継承せず、新しい役割ドメインも導入しません.Lambda式は文法的役割ドメインに基づいています.つまり、lambda式関数体の変数とその外部環境の変数は同じ意味を持っています.(lambda式の形式パラメータも含む).また、thisキーワードとその参照は、Lambda式の内部と外部でも同じ意味を持つ.
Lambda式全文アドレス:http://blog.csdn.net/sun_promise/article/details/51121205
1.ローカル変数へのアクセス
1)lambda式で外層のローカル変数に直接アクセスできる
eg:
final int num = 1;
Converter s =
        (param) -> String.valueOf(param + num);
 
s.convert(2);     // 3

ただし、匿名オブジェクトとは異なり、lambda式のローカル変数(eg:num)はfinalとして宣言する必要はありません.
eg:
int num = 1;
Converter s =
        (param) -> String.valueOf(param + num);
 
stringConverter.convert(2);     // 3

ただし、ここでのローカル変数(eg:num)は、後続のコードによって変更されてはならない(すなわち、finalの意味を隠す)
eg:次のコードはコンパイルできません
int num = 1;
Converter s =
        (param) -> String.valueOf(param + num);
num = 5;

Note:Lambda式でローカル変数を変更しようとすることは許されません.
2)Lambda式で参照される変数の値は変更できません.
public void repeat(String string, int count) {
        Runnable runnable = () -> {
            for (int i = 0; i < count; i++) {
                string = string + "a";//    
                System.out.println(this.toString());
            }
        };
        new Thread(runnable).start();
}

3)Lambda式では、ローカル変数と同じ名前のパラメータまたはローカル変数を宣言することはできません.
eg:
String first = "";
Comparator comparator = (first, second) -> Integer.compare(first.length(),//     
                second.length());

2.アクセスオブジェクトフィールドと静的変数
ローカル変数とは異なり、Lambda内部のインスタンスのフィールド(すなわち、メンバー変数)および静的変数は、読み取り可能で書き込み可能である.
eg:
class LambdaDemo {
    static int myStaticNum;
    int myNum;
 
    void testScopes() {
        Converter s1 = (param) -> {
            myNum = 33;
            return String.valueOf(param);
        };
 
        Converter s2 = (param) -> {
            myStaticNum = 87;
            return String.valueOf(param);
        };
    }
}

3.インタフェースにアクセスできないデフォルトの方法
Lambda式ではデフォルトメソッドにアクセスできません.
4.Lambda式のthis
Lambda式でthisを使用すると、Lambda式を作成する方法のthisパラメータが参照されます.
eg:
public class Test2 {
    public static void main(String[] args) {
        Test2 test = new Test2();
        test.method();
    }
    @Override
    public String toString() {
        return "Lambda";
    }
    public void method() {
        Runnable runnable = () -> {
            System.out.println(this.toString());
        };
        new Thread(runnable).start();
    }
}

結果を表示:Lambda
5.Lambda式の役割ドメインを総合的に理解する
(注:上記の3つの内容が理解されている場合は、次の例や分析をよく見る必要はありません.)
eg:
public class Test {
    public static void main(String[] args) {
        repeatMessage("Hello world", 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式では定義されず、メソッドのパラメータです.Lambda式はrepeatMessage()が返されてから実行される可能性がありますが、パラメータ変数は消えています.textとcount変数を保持したらどうなりますか?
上記の状況をよりよく理解するためには,Lambda式をより深く理解する必要がある.
lambda式には、次の3つのセクションがあります.
  • のコードです.
  • パラメータ.
  • 自由変数の値.ここで、「自由」とは、パラメータではなく、コードに定義されていない変数を指す.

  • 例では、lambda式には2つの自由変数、text、countがあります.データ構造は、Lambda式がこの2つの変数の値を格納する必要があることを示します.すなわち、「Hello world」
    」と20.これらの値はLambda式によって取得されたと理解できます(これは技術的な実装の詳細です.eg:lambda式を1つの方法しか含まれていないオブジェクトに変換することができ、自由変数の値がオブジェクトのインスタンス変数にコピーされます).
    Note:自由変数を含むコードブロックは「クローズドパッケージ(closure)」と呼ばれます.Javaではlambda式がクローズドパッケージです.実際、内部クラスはずっとクローズドパッケージです.Java 8ではクローズドパッケージにもっと魅力的な構文が付与されています.
    Lambda式は、閉じた役割ドメインの変数値をキャプチャします.Javaでは、取得された値が良好に定義されていることを確認するために、重要な制約を遵守する必要があります.Lambda式では、参照される変数の値は変更できません.
    eg:次の式は合法的ではありません.
    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がインスタンス変数または閉じたクラスの静的変数である場合、結果が同じように定義されていなくてもエラーは報告されません.同様に、共有オブジェクトを変更することも完全に合法的であり、それが適切でなくても.
    List matches = new ArrayList<>();
    for(Path p: files)
    //     matches  ,            
    new Thread(() -> {if(p       ) matches.add(p);}).start();

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

    1つの方法では、同じ名前のローカル変数を2つ持つことはできません.そのため、lambda式にこのような変数を導入することはできません.
    Lambda式でthisキーを使用すると、Lambda式を作成する方法のthisパラメータを参照します.
    eg:
    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がメソッドのどこにあるかにかかわらず,その意味は同じである.