2015-01-02 13 views
23

私はJava 8の学習中ですが、私は少し奇妙なものを見つけました。Java 8ローカル変数で呼び出されたメソッド参照

は、次のコードを考えてみましょう:

private MyDaoClass myDao; 

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) 
{ 
    RelationshipTransformer transformer = new RelationshipTransformerImpl(); 

    myDao.createRelationships(
      relationships.stream() 
      .map((input) -> transformer.transformRelationship(input)) 
      .collect(Collectors.toSet()) 
    ); 
} 

は基本的に、私は私が使用しているDAOのAPIに準拠し するために、異なるタイプのrelationshipsと呼ばれる入力セットをマップする必要があります。変換のために、ローカル変数としてインスタンス化する既存のRelationshipTransformerImplクラスを使用したいと思います。

は今、ここに私の質問です:

私は次のように上記のコードを変更することだった場合:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) 
{ 
    RelationshipTransformer transformer = new RelationshipTransformerImpl(); 

    myDao.createRelationships(
      relationships.stream() 
      .map((input) -> transformer.transformRelationship(input)) 
      .collect(Collectors.toSet()) 
    ); 

    transformer = null; //setting the value of an effectively final variable 
} 

ローカル変数transformer「は事実上なくなっているので、私は明らかに、コンパイルエラーを取得しないだろう最後の"。ただし、ラムダをメソッドリファレンスで置き換える場合は、

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) 
{ 
    RelationshipTransformer transformer = new RelationshipTransformerImpl(); 

    myDao.createRelationships(
      relationships.stream() 
      .map(transformer::transformRelationship) 
      .collect(Collectors.toSet()) 
    ); 

    transformer = null; //setting the value of an effectively final variable 
} 

私はもはやコンパイルエラーを受け取りません!なぜこれが起こるのですか?ラムダ式を書く2つの方法は同等でなければならないと思っていましたが、明らかに何かが続いています。

答えて

18

JLS 15.13.5は説明を保持することができる:

方法基準発現評価のタイミングは、ラムダ式(§15.27.4)のそれよりも複雑です。メソッド参照式が:: separatorの前に(型ではなく)式を持つ場合、その部分式は直ちに評価されます。評価の結果は、対応する機能インタフェースタイプのメソッドが呼び出されるまで格納されます。その時点で、結果は呼び出しのターゲット参照として使用されます。つまり、:: separatorより前の式は、プログラムがメソッド参照式に遭遇したときにのみ評価され、その後の関数型の呼び出しでは再評価されません。

あなたのケースでは、transformerが:: separatorの前の式であるため、一度だけ評価されて保存されます。参照されたメソッドを呼び出すために再評価する必要はないので、後でtransformerにnullを割り当てることは重要ではありません。

+0

なぜラムダのケースで同じことをすることができなかったのかわかりません:lambdaが評価されたときに 'transformer'(または他の変数)の値を取得し、内部の' final'フィールドに格納します。これは、メソッドの参照で行わなければならない効果があります。その変数をラムダ式の本体の外側でのみ変更できるように制限することができます。私は何かが欠けているように感じる。 –

+1

@SotiriosDelimanolisラムダはすぐには評価されません。コンパイル時に生成されるコールサイトだけです。実際のリンケージは、ラムダが 'invokedynamic'で初めて初めて発生します。 – fge

+0

@fge私は_invoked_を意味しませんでした。ラムダ式は、機能インタフェースのインスタンスを生成するために評価されます(http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.4)。コンパイラ(そしてJVM)は 'final'変数の値を単純にクローズし、ローカルに格納することができます。この場合、 –

5

ワイルドな推測が、私には、ここで

は、コンパイラが作成されたストリームがすべてで同期していると主張することはできません...何が起こるかです。これは可能なシナリオとして考えられます。

  • ストリームを作成するrelationships引数から、
  • reaffect transformer;
  • ストリームを展開します。

コンパイル時に生成されるのはコールサイトです。ストリームが展開されたときにのみリンクされます。

最初のラムダではローカル変数を参照していますが、この変数はではなく、コールサイトのです。

2番目のラムダでは、メソッド参照を使用するため、生成されたコールサイトはメソッドへの参照を保持する必要があり、そのメソッドを保持するクラスインスタンスを保持する必要があります。後で変更するローカル変数によって参照されたということは重要ではありません。

私の2セント...

4

最初の例では、マッピング関数が呼び出されるたびにtransformerが参照されるため、すべての関係で1回呼び出されます。

transformerは、transformer::transformRelationshipmap()に渡されたときに一度だけ参照されます。後でそれが変わるかどうかは関係ありません。

者はが、ラムダ式と方法参照ない「ラムダ式を記述するための2つの方法」、言語2つの異なる特徴です。

関連する問題