web-dev-qa-db-ja.com

ラムダ式には、コード行を保存する以外の用途はありますか?

ラムダ式には、コード行を保存する以外の用途はありますか?

解決が簡単ではなかった問題を解決するための、ラムダによる特別な機能はありますか?私が見た典型的な用法はこれを書く代わりにそれです:

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

コードを短くするためにラムダ式を使うことができます。

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());
116
Vikash

ラムダ式は、一般的にJavaで解決できる一連の問題を変更するものではありませんが、アセンブリ言語でプログラミングしなくなったのと同じ理由で、確実に特定の問題の解決を容易にします。余計なタスクをプログラマーの作業から取り除くことで、作業が楽になり、他の方法では触れられないようなことを、手動で作成する必要があるコードの量だけで実行できるようになります。

しかし、ラムダ式はコード行を節約するだけではありません。ラムダ式では、関数を定義することができます。これは、以前は回避策として匿名の内部クラスを使用できるため、これらの匿名の内部クラスを置き換えることができます。しかし、一般的ではありません。

特に、ラムダ式は、変換される機能インターフェースとは独立して定義されているため、アクセスできる継承メンバーはなく、さらに機能インターフェースを実装する型のインスタンスにアクセスすることもできません。ラムダ式の中では、thissuperは周囲の文脈と同じ意味を持ちます。 this answer も参照してください。また、周囲のコンテキストのローカル変数を隠蔽する新しいローカル変数を作成することはできません。関数を定義するという意図されたタスクのために、これは多くのエラーソースを取り除きますが、それは他のユースケースのために、たとえ関数型インタフェースを実装したとしてもラムダ式に変換できない匿名内部クラスがあるかもしれません。

さらに、構造体new Type() { … }は、(newが常に行うように)新しい個別のインスタンスを生成することを保証します。匿名の内部クラスインスタンスは、static以外のコンテキストで作成された場合、常に外部インスタンスへの参照を保持します。対照的に、ラムダ式は、必要に応じて、つまりthisまたは非thisメンバーにアクセスする場合にのみ、staticへの参照をキャプチャーします。そしてそれらは意図的に特定されていないアイデンティティのインスタンスを生成します。これは実行時に既存のインスタンスを再利用するかどうかを実装が決定することを可能にします( を参照) ? ”)。

これらの違いはあなたの例に当てはまります。あなたの(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())は、典型的な実装ではシングルトンに評価される非キャプチャーラムダ式ですが、あなたの匿名のインナークラスコンストラクトは常に新しいインスタンスを生成します、またそれは外側のインスタンスへの参照をキャプチャーするかもしれません。さらに、それはあなたのハードドライブに.classファイルを生成しません。

セマンティックとパフォーマンスの両方の違いを考えると、ラムダ式は将来、プログラマが将来の特定の問題を解決する方法を変える可能性があります。もちろん、新しいAPIが新しい言語機能を利用する関数型プログラミングのアイデアを取り入れるためです。 Java 8のラムダ式とファーストクラスの値 も参照してください。

114
Holger

プログラミング言語は、実行するマシン用ではありません。

それらはプログラマーのためのものです考える in。

言語は、私たちの考えを機械が実行できるものに変えるためのコンパイラとの会話です。他の言語からやって来る人々(あるいはleave it for other languages)から寄せられたJavaに関する主な不満の1つは、それが強制 =プログラマ上の特定のメンタルモデル(つまり、すべてがクラスです)。

それが良いものか悪いものかを判断するつもりはありません。すべてがトレードオフです。しかし、Java 8のラムダを使用すると、プログラマは関数という点で考えるを使用できます。これは、以前はJavaではできなかったことです。

それは手続き型プログラマーがJavaに来るときクラスに関して 考えるを学ぶことと同じことです。あなたはそれらが徐々に構造化された構造からなるクラスから動くのを見ますメソッドを作成し、合理的なOOデザイン(mea culpa)によく似たものに進みます。

匿名の内部クラスを表現するためのより短い方法としてそれらを考えると、おそらく上の手続き型プログラマーがクラスが大きな改善であるとは思わなかったのと同じ方法でそれらを非常に印象的に見つけることはないでしょう。

49
Jared Smith

大量のロジックを短く明確に記述することができれば、他の人が読んで理解するのにかかる時間が短縮されるため、コード行を節約することは新機能と見なすことができます。

ラムダ式(および/またはメソッド参照)がないと、Streamパイプラインは読みにくくなります。

たとえば、各ラムダ式を匿名クラスのインスタンスに置き換えた場合、次のStreamパイプラインがどのようになったかを考えてみてください。

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

それはそのようになります:

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

これはラムダ式のバージョンよりも書くのがはるかに難しく、そして間違いが起こりやすくなります。理解も難しいです。

そしてこれは比較的短いパイプラインです。

これをラムダ式やメソッド参照なしで読めるようにするには、ここで使用されているさまざまな機能インタフェースインスタンスを保持する変数を定義する必要があります。

35
Eran

内部反復

Javaコレクションを繰り返すとき、ほとんどの開発者は要素を取得し、次に処理を行う傾向があります それ。これは、その項目を取り出して使用する、または再挿入するなどです。8より前のバージョンのJavaでは、内部クラスを実装して次のようなことができます。

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

Java 8では、以下のものを使用して、より優れた冗長性を実現できます。

numbers.forEach((Integer value) -> System.out.println(value));

以上

numbers.forEach(System.out::println);

議論としての行動

次のような場合を考えます。

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    } 
    return total;
}

Java 8 Predicate interface を使えば、こんなふうにできます。

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

それを呼び出すと:

sumAll(numbers, n -> n % 2 == 0);

出典:DZone - なぜJavaでラムダ式が必要なのか

8
alseether

以下のように、内部クラスの代わりにラムダを使用することには多くの利点があります。

  • より多くの言語構文セマンティクスを導入せずに、コードをよりコンパクトで表現力豊かにします。あなたはすでにあなたの質問に例を挙げています。

  • ラムダを使用することで、コレクションに対するmap-reduce変換など、要素のストリームに対する機能的なスタイルの操作を使ってプログラミングすることができます。 Java.util.functionJava.util.stream パッケージのドキュメントを参照。

  • コンパイラーによってラムダ用に生成された物理クラスファイルはありません。したがって、配布されるアプリケーションが小さくなります。 メモリはどのようにラムダに割り当てますか?

  • ラムダがそのスコープ外の変数にアクセスしない場合、コンパイラはラムダ作成を最適化します。つまり、ラムダインスタンスはJVMによって一度だけ作成されます。詳細については、@ Holgerの質問に対する答えを参照してください Java 8ではメソッド参照のキャッシュは良い考えですか?

  • Lambdasは機能的インタフェース以外にマルチマーカーインタフェースを実装することができますが、匿名内部クラスはそれ以上のインタフェースを実装することはできません。例えば:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};
    
4
holi-java

まだ言及していないことの1つは、ラムダを使用して機能を定義できることです

単純な選択機能があれば、定型句を使って別の場所に配置する必要はありません。簡潔でローカルに関連性のあるラムダを書くだけです。

2
Carlos

あなたの質問に答えるために、実際のところ問題はラムダですしないでくださいあなたがJava-8の前にあなたがすることができなかったことをやらせますもっと簡潔なコードを書くことができます。これの利点は、あなたのコードがより明確でより柔軟になるということです。

2
Aomine

はい、たくさんの利点があります。

  • クラス全体を定義する必要はありません。リファレンスとして関数それ自身の実装を渡すことができます。
    • 内部でクラスを作成すると.classファイルが作成されますが、lambdaを使用する場合は、クラスの作成はコンパイラによって回避されます。これは、lambdaではclassの代わりに関数の実装を渡すためです。
  • コードの再利用性は以前よりも高い
  • そしてあなたが言ったように、コードは通常の実装より短いです。
1
Sunil Kanzar

ラムダは、無名クラスの単なる構文上の糖です。

ラムダの前は、無名クラスを使って同じことを達成できます。すべてのラムダ式は無名クラスに変換できます。

IntelliJ IDEAを使用している場合は、変換を実行できます。

  • カーソルをラムダに置く
  • Alt/option + enterを押す

enter image description here

1
Sweeper