15

私はキャメルを学び始めたばかりで、最初に目にするのは

    context.addRoutes(new RouteBuilder() {
        public void configure() {
            from("file:data/inbox?noop=true").to("file:data/outbox");
        }
    });

私は(合理的にIMHO)置き換えようとしています

    context.addRoutes(()->from("file:data/inbox?noop=true").to("file:data/outbox"));

しかし、それは有効ではありません。

掘り下げていくと、ラムダが機能インターフェースに適用されることを発見しましたが (インターフェースが適格である場合、これは暗示されます)、 @FunctionalInterface アノテーションはインターフェースにのみ適用できる (十分に公平です) こと、そして私が知る限り、抽象クラスに相当する注釈はありません。もちろん、RouteBuilder は抽象クラスです。

ラムダがインターフェイスに制限されているのはなぜですか?

「機能クラス」を安全でない/予測不可能/不合理にするインターフェイスとクラスの本質的な違いは何ですか?

抽象メソッドを公開する必要があるなどの修飾子があったかどうかは理解できましたが、上記が不合理である理由を説明するのに途方に暮れています。

4

3 に答える 3

22

これは、JSR-335 専門家グループで最も困難で広範囲に議論された決定の 1 つです。一方では、単一の抽象メソッドの抽象クラスがラムダの適切な変換ターゲットになる可能性があることは完全に合理的であるように思われます。そして、あなたのメンタルモデルが「ラムダは単なるコンパクトな匿名クラス」である場合、これは完全に合理的な考えでした。

ただし、この文字列をしばらく引っ張ると、少数のユースケースのために、多くの複雑さと制約が発生することに気付きます。

これが引きずる最悪のことの 1 つは、ラムダ本体内の名前の意味であり、特殊なケースとして、this. 内部クラスの本体内には、内部クラス内の名前がスーパータイプのメンバーを参照したり、レキシカル環境からキャプチャされたりする可能性があるため、非常に複雑な検索規則 (「くし型検索」) があります。(たとえば、多くのバグや謎解きthisは、Outer.this、内部クラス本体で。) 抽象 SAM クラスへのラムダ変換を許可した場合、2 つの不適切な選択肢があります。内部クラスのひどい名前検索の複雑さですべてのラムダを汚染するか、抽象クラスターゲットへの変換を許可しますが、ラムダ本体が基本クラスのメンバーを参照できないようにアクセスを制限します (これは、独自の混乱を引き起こします)。結果として得られるルールは非常にきれいです。ラムダ パラメーターの形式を除いてthis、ラムダ本体内の名前 (単なる名前である を含む) は、ラムダ本体のすぐ外側で意味するものを正確に意味します。

ラムダを内部クラスに変換する際のもう 1 つの問題は、オブジェクト ID とそれに伴う VM 最適化の損失です。内部クラス作成式 (例: new Foo() { }) は、一意のオブジェクト ID を持つことが保証されています。ラムダのオブジェクト ID にそれほど強くコミットしないことで、VM を解放して、他の方法では行うことができなかった多くの有用な最適化を行うことができます。(結果として、ラムダ リンケージとキャプチャは、匿名クラスよりも既に高速です。さらに、まだ適用していない最適化の深いパイプラインが残っています。)

さらに、単一の抽象メソッドの抽象クラスがあり、ラムダを使用してそれらを作成できるようにしたい場合は、これを有効にする簡単な方法があります。関数インターフェイスを引数として受け取るファクトリ メソッドを定義します。ThreadLocal(これを行う Java 8 のファクトリ メソッドを追加しました。)

「ラムダはオブジェクトにとって便利な構文にすぎない」という世界観の棺桶の最後の釘は、既存のコードベースと単一抽象メソッド インターフェイスと抽象クラスの使用の分析を行った後に得られました。抽象クラスに基づいているのはごくわずかな割合であることがわかりました。すべてのラムダに、使用の 1% 未満しか恩恵を受けないアプローチの複雑さとパフォーマンスの問題を負わせるのはばかげているように思えました。そのため、他の 99% 以上のメリットを享受するために、このユース ケースを切り離すという「勇敢な」決定を下しました。

于 2015-07-18T13:42:49.503 に答える
8

ラムダ式は、メソッドではなく関数を定義します。それらの間には明らかに技術的な関係がありますが、概念的な見方と、ソース コード レベルでの動作が異なります。

たとえば、ラムダ式は、最終的に実装する型からメンバーを継承しません。したがって、あなたの場合、RouteBuilderラムダ式はfrom呼び出したいメソッドを継承しないため、機能的なインターフェースであっても機能しません。同様に、 と の意味はthissuperラムダ式の外側と同じであり、後で関数を表すインスタンス (つまりインスタンス) を参照しませんRouteBuilder

とは言っても、機能を拡張してs のabstractように動作するクラスを実装することは不合理ではありませんがinterface、チェックが難しいいくつかの制約が課せられます。クラスに正確に 1 つのメソッドとアクセス可能な引数なしのコンストラクターがあることを確認するのは簡単ですが、クラスにはabstract変更可能な状態がなく、そのクラスのインスタンスの構築にも副作用がない必要があります。ラムダインスタンスをキャッシュして再利用し、異なる作成サイト間でそれらを共有する JVM の自由は、プログラムの動作に影響を与えません。

abstract classこれを確認するのは難しく、ほとんどの場合、制約が満たされていないのは、それがそもそもではなくを使用する理由だからinterfaceです。ラムダ式が内部クラスの単なる置換として定義されているため、インスタンスの共有と再利用が許可されていない場合は機能する可能性がありますが、単純な内部クラスの置換として使用されている場合でも、ラムダ式はそうではありません多くの場合、関数型プログラミングについて考えずに…</p>

于 2015-07-17T19:12:09.330 に答える
6

他の素晴らしい回答に加えて、そのようなRouteBuilderオブジェクトを非常に頻繁に作成する必要がある場合は、次のようなヘルパー メソッドを作成できることに注意してください。

public static RouteBuilder fromConfigurator(Consumer<RouteBuilder> configurator) {
    return new RouteBuilder() {
        public void configure() {
            configurator.accept(this);
        }
    }
}

そして、次のように使用します。

context.addRoutes(fromConfigurator(
    rb->rb.from("file:data/inbox?noop=true").to("file:data/outbox")));
于 2015-07-19T03:44:33.323 に答える