27

私は 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())
    );
}

基本的に、relationships使用している DAO の API に準拠するために、呼び出された入力セットを別の型にマップする必要があります。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 つの方法は同等であるはずだと思いましたが、明らかにそれ以上のことが起こっています。

4

3 に答える 3

24

JLS 15.13.5には次の説明が含まれている場合があります。

メソッド参照式の評価のタイミングは、ラムダ式の場合よりも複雑です (§15.27.4)。メソッド参照式の :: 区切り記号の前に (型ではなく) 式がある場合、その部分式はすぐに評価されます。評価の結果は、対応する機能インターフェース型のメソッドが呼び出されるまで保存されます。その時点で、結果は呼び出しのターゲット参照として使用されます。これは、:: 区切り文字の前の式は、プログラムがメソッド参照式に遭遇した場合にのみ評価され、その後の機能インターフェイス型での呼び出しでは再評価されないことを意味します。

私が理解しているように、あなたの場合transformerは :: 区切り記号の前の式であるため、一度だけ評価されて保存されます。transformer参照されたメソッドを呼び出すために再評価する必要がないため、後で null が割り当てられても問題ありません。

于 2015-01-02T20:26:39.440 に答える
5

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

2番目の例では、に渡されtransformerたときに一度だけ参照されます。だから後から変わっても構わない。transformer::transformRelationshipmap()

これらは「ラムダ式を記述する 2 つの方法」ではなく、ラムダ式とメソッド参照であり、言語の 2 つの異なる機能です。

于 2015-01-02T20:33:26.610 に答える
5

ワイルドな推測ですが、私には、ここで何が起こるか...

コンパイラは、作成されたストリームが同期的であると断言できません。これを可能なシナリオと見なします。

  • relationships引数からストリームを作成します。
  • 再影響しtransformerます。
  • ストリームが展開されます。

コンパイル時に生成されるのは呼び出しサイトです。ストリームがアンロールするときにのみリンクされます。

最初のラムダでは、ローカル変数を参照していますが、この変数は呼び出しサイトの一部ではありません。

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

私の2セント...

于 2015-01-02T20:20:41.150 に答える