web-dev-qa-db-ja.com

古典的な操作の列挙型の例におけるラムダ

ご存じのように、Operation enumの古典的な例があります(ただし、Java 8標準インターフェースを使用))。これは次のとおりです。

enum Operation implements DoubleBinaryOperator {
    PLUS("+") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left + right;
        }
    },
    MINUS("-") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left - right;
        }
    },
    MULTIPLY("*") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left * right;
        }
    },
    DIVIDE("/") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left / right;
        }
    };

    private final String symbol;

    private Operation(final String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return symbol;
    }
}

テスト済み:

Arrays.stream(Operation.values())
        .forEach(op -> System.out.println("Performing operation " + op.getSymbol() + " on 2 and 4: " + op.applyAsDouble(2, 4)));

それは提供します:

2と4の操作+の実行:6.0
操作の実行-2および4で-2.0
2および4で操作を実行*:8.0
操作の実行/ 2および4で:0.5

しかし、私はJava 8でより良いことができるように感じるので、以下を実装しました:

enum Operation implements DoubleBinaryOperator {
    PLUS    ("+", (l, r) -> l + r),
    MINUS   ("-", (l, r) -> l - r),
    MULTIPLY("*", (l, r) -> l * r),
    DIVIDE  ("/", (l, r) -> l / r);

    private final String symbol;
    private final DoubleBinaryOperator binaryOperator;

    private Operation(final String symbol, final DoubleBinaryOperator binaryOperator) {
        this.symbol = symbol;
        this.binaryOperator = binaryOperator;
    }

    public String getSymbol() {
        return symbol;
    }

    @Override
    public double applyAsDouble(final double left, final double right) {
        return binaryOperator.applyAsDouble(left, right);
    }
}

機能的には同等ですが、両方の実装は似ていますか、それとも古いバージョンと同じように新しいバージョンを悪化させるいくつかの隠された詳細がありますか?

そして最後に、Java 8)の時点で、ラムダ方式が推奨される方法ですか?

35
skiwi

明らかに、ラムダ版ははるかに読みやすくなっています。短いだけでなく、コンストラクターで実装オペレーターを一目で確認できるようになります。 enumを拡張してint計算もサポートすることを想像してみてください…

パフォーマンスの観点からは、生成されたラムダクラスによって匿名のenum内部クラスを交換しています。ラムダバージョンでは、委任のレベルがもう1つ追加されますが、HotSpotオプティマイザーにとってそれは難しいことではありません。実行パフォーマンスに関して違いが見られることはほとんどありません。

ただし、その結果としてラムダパターンを適用すると、クラスを使用するアプリケーションのstartupが高速化される場合があります。その理由は、従来の特殊なenumアプローチの場合、Javaコンパイラは、ファイルシステムまたは(場合によってはZip圧縮された)いずれかに存在するケースごとに内部クラスを生成する必要があるためです。 Jarファイル内。ラムダクラス(非常に単純な構造を持つ)のバイトコードをオンザフライで生成する方が、通常、クラスをロードするよりも高速です。生成されたラムダクラスのアクセスチェックがないことも役立ちます。

要約すると:

  • ラムダアプローチは読みやすく、コードは保守しやすい(大きな点)
  • 実行性能はほぼ同じです
  • ラムダアプローチの場合、起動時間は短くなる可能性があります

ラムダにとっては大きな勝利です。はい、私はラムダの方法がJava 8。

25
Holger

betterの定義方法によって異なります。

あなたの場合と私の意見では、ラムダは純粋な勝利です。既存のJDK関数の一部を再利用することもできます。例:

enum Operation implements DoubleBinaryOperator {
    PLUS    ("+", Double::sum),
    ...
}

これは短くて読みやすいです。コードのベンチマークを行わない場合のパフォーマンスについて、妥当なことは何も言えないと思います。

ラムダはinvokeDynamicで実装され、呼び出しサイトを実際のコードに動的にリンクして実行します。匿名の内部クラスはありません。

19
emesx

誰もこれについて言及していないことに驚いていますが、ラムダアプローチは、複数のメソッドを実装するときに厄介なものになる可能性があります。名前のないラムダの束をコンストラクタに渡すと、より簡潔になりますが、必ずしも読みやすくなるとは限りません。

また、関数のサイズが大きくなるにつれて、ラムダを使用する利点は減少します。ラムダが数行を超える場合、オーバーライドは読みやすいかもしれませんが、読みやすいかもしれません。

5
shmosel

悪いことを定義します。ほとんどの場合、バイトコードが少し多く使用され、少し遅くなります。

これらがあなたにとって重要でない限り、私はあなたがより明確でよりシンプルであると思うアプローチを使用します。

5
Peter Lawrey