2017-07-12 8 views
3

私はこの動作を理解していません。ラムダのクラスフィールドを使用

コードのこの作品は、準拠しています

public class A { 

    private String s; 

    private Function<String, String> f = e -> s; 

    public A(String s) { 
     this.s = s; 
    } 
} 

しかし、私はsが最終作れば、私は、コンパイラのエラーを取得:

public class A { 

    private final String s; 

    private Function<String, String> f = e -> s; // Variable 's' might not have been initialized 

    public A(String s) { 
     this.s = s; 
    } 
} 

それはなぜですか?それ以外の場合、私は理解しますが、フィールドfinal(コンストラクタでその値を初期化する必要があります)を宣言すると、コンパイラが不平を言って、それがどうでしょうだい、final

答えて

11

、この例では、同じエラーがあります。

public class Test { 
    private final String a; 
    private String b = a; // // Variable 'a' might not have been initialized 

    public Test(String a) { 
     this.a = a; 
    } 
} 

宣言の場所での初期化は、コンストラクタの前に実行されるからです。したがって、bの宣言の場所では、aはまだ初期化されていません。

あなたはこの例を使用する場合それは明らかだ:あなたはそれを実行するとbの初期化が前に行わを取ったので、

public class Test { 
    private String a = "init"; 
    private String b = a; 

    public Test(String a) { 
     this.a = a; 
    } 

    public static void main(String[] args) { 
     System.out.println(new Test("constructor").b); 
    } 
} 

、それは"init"(フィールドaが最初に割り当てられていたために値)とない"constructor"を印刷しますコンストラクタを実行しています。

aへのアクセスが延期されているので、コンパイラでは正常に動作すると予想されるため、例のlambdaはそれを少し複雑にしますが、コンパイラは「don初期化される前に変数にアクセスしないでください。 "

あなたはアクセサメソッド使用して、それを回避することができます行うに

public class Test { 
    private final String a; 
    private String b = getA(); // allowed now, but not very useful 
    private Function<String, String> f = e -> getA(); // allowed now and evaluated at the time of execution of the function 

    public Test(String a) { 
     this.a = a; 
    } 

    public static void main(String[] args) { 
     System.out.println(new Test("constructor").b); // prints "null" 
     System.out.println(new Test("constructor").f.apply("")); // prints "constructor" 
    } 

    public String getA() { 
     return a; 
    } 
} 
+3

ラムダ式の基本的な性質は、周囲のコンテキストでの式のように機能するため、空白の最終変数へのアクセスに関する規則は同じです。これは内部クラスとは異なります。 'getA()'を呼び出すのではなく、 'Test.this.a 'を使って' a'にアクセスすることで回避できます。 – Holger

+1

@Holgerありがとう、あなたがリンクした答えは非常に興味深く、はっきりしています。興味深いことに、 'a'と' this.a 'を介して 'a'にアクセスすることはエラーですが、' Test.this.a'はラムダの場合はエラーではなく、 'b'への割り当ての場合でもエラーです。 –

+2

ほとんどすべてのJDK 8および9で 'b = Test.this.a; 'を試してみました。 [仕様](https://docs.oracle.com/javase/specs/jls/se8/html/jls-16.html)は、「*変数の単純な名前(またはフィールドの場合は、空白の最終変数へのアクセスを禁止する場合は '*'で修飾されたフィールドの単純名)。あなたはEclipseを使いましたか? – Holger

5

最終的なメンバでない変数は、(String変数の場合はデフォルト値-null)常に初期化されるため、初期化されていない可能性はありません。

一方、最終変数は1回しか初期化できないため、デフォルト値に初期化されていないと仮定しています。そうでなければ、コンパイルを取得しますので、私は、我々は最後の変数はデフォルト値が割り当てられていないことを意味するために、この最後の文を理解することができると仮定し

4.12.4. final Variables

A variable can be declared final. A final variable may only be assigned to once. It is a compile-time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment

私が見つけた最も近い関連する事がJLS 4.12.4.であります時間エラーはthis.s = s;です。

より良いJLSの参照(ホルガーのおかげで)JLS 16です:

Chapter 16. Definite Assignment

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

この要件の背後にある合理的にsが初期化される前に(あなたの例では)ラムダ式を呼び出すことができることである:

public A(String s) { 
    String v = f.apply("x"); // this.s is not initialized at this point 
          // so it can't be accessed 
    this.s = s; 
} 

最終変数を初期化した後でコンストラクタ内のラムダ式を初期化することができます(引数の名前をメンバー変数と異なるように変更したので、ラムダ式はgr )ローカル変数というAB:それはラムダとは何の関係もありません

public A(String so) { 
    // f = e -> s; // Error: The blank final field s may not have been initialized 
    this.s = so; 
    f = e -> s; // works fine 
} 
+3

[JLS§16](https://docs.oracle.com/javase/ "**ローカル変数またはブランク' final'フィールド 'x'のアクセスごとに、' x'はアクセスの前に確実に割り当てられなければなりません。コンパイル時エラーが発生します。** " – Holger

+0

@Holger thanks! – Eran

0

これも可能な方法を

public class A { 

    private final String s; 
    private Function<String, String> f; 

    public A(String s) { 
    this.s = s; 
    this.f = e -> s; 
    } 
} 
関連する問題