blog

なぜラムダ式を使うのか?

ラムダ式を使用する目的 ラムダ式 "とは、1回または複数回実行することができる、受け渡し可能なコードの一部です。構文を学ぶために、Javaでいつも使われている似たようなコードブロックを復習してみましょ...

Dec 7, 2020 · 7 min. read
シェア

ラムダ式の使用目的

ラムダ式」とは、1回または複数回実行されるように渡されるコードの一部です。構文を学ぶには、Javaで使われてきた同様のコードブロックを復習してください。

何らかのロジックを別のスレッドで実行する場合、Runnableインターフェイスを実装したクラスのrunメソッドにコードを記述するのが一般的です:

class Worker implements Runnable {public void run() {for (int i = 0; i < 1000; i++)doWork();}...}

そして、このコードを実行しようとすると、Workerクラスのインスタンスが生成されます。このインスタンスはスレッドプールにコミットされるか、シンプルにするために新しいスレッドが開始されます:

Worker w = new Worker();new Thread(w).start();

このコードで重要なのは、runメソッドに別のスレッドで実行させたいコードが含まれていることです。カスタムコンパレータによるソートを考えてみましょう。デフォルトの辞書順ではなく、文字列の長さでソートしたい場合は、sortメソッドにComparatorオブジェクトを渡します:

class LengthComparator implements Comparator<String> {public int compare(String first, String second) {return Integer.compare(first.length(), second.length());}}Arrays.sort(strings, new LengthComparator());

sort メソッドは、配列が完全に整列するまで compare メソッドをコールし続け、 並び替えが行われます。あなたは要素を比較するコードの断片をソートメソッドに渡しているのですが、そのコードはソートロジックに組み込まれます。

注意: x=yの場合、Integer.compare(x,y)は0を返し、xyの場合は正の数を返します。この静的メソッドはJava 7で追加されました。また、x-y は、x と y のサイズを比較するために使用すべきではありません。この計算は、大きな逆符号のオペランドに対してオーバーフローする可能性があるからです。

ボタンのコールバックも実行を遅らせるものの一例です。リスナーインターフェースを実装したクラスのメソッドにコールバックアクションを記述し、インスタンスを生成してボタンに登録します。この場合、多くの開発者は "匿名クラスの匿名インスタンス "を使います:

button.setOnAction(new EventHandler<ActionEvent>() {public void handle(ActionEvent event) {System.out.println("Thanks for clicking!");}});

ここで重要なのは、コードがhandleメソッドの中にあるということです。このコードはボタンがクリックされたときに実行されます。

注:Java 8ではJavaFXがSwing GUIの次の後継になったので、これらの例ではJavaFXを使いますが、SwingでもJavaFXでもAndroidでも、ボタンがクリックされたときに実行できるようにするには、ボタンにコードを追加する必要があります。

この3つの例では、同じアプローチが見られます。スレッドプール、ソートメソッド、ボタンなどです。このコードは後で呼び出されます。

これまでJavaでは、コードの一部を他のコードに渡すことは簡単ではありませんでした。コードのブロックを渡すことはできなかったのです。Javaはオブジェクト指向言語なので、必要なコードを含むメソッドを持つクラスに属するオブジェクトを構築しなければなりませんでした。

他のいくつかの言語では、コードブロックを直接使用することが可能です。長い間、Javaの設計者はこの機能を搭載することを拒んでいました。結局のところ、Javaの大きな強みの1つは、そのシンプルさと一貫性です。もし言語が、コードを少しでも単純にする機能をすべて含んでいたら、メンテナンス不可能になってしまいます。しかし、スレッドの生成やボタンのクリックを登録するコードが単純になったというだけでなく、他の言語ではAPIの多くがより単純で、一貫性があり、強力になっています。似たようなAPIや機能がJavaでもクラスやオブジェクトによって実装されていますが、これらのAPIは使いやすくも快適でもありません。

ラムダ式の本質

[capture list](parameter list)->return type{function body}

引数リストも戻り値の型も省略できます。捕捉リストは空でも構いませんが、[]は必ず記述しなければなりません。このように、ラムダ式が通常の関数と異なる点は、関数名を持たないことに加えて、キャプチャーリストを持つことです。このキャプチャーリストがあるからこそ、その制限を解決することができるのです。キャプチャー・オブジェクトとパラメータの違いは、キャプチャー・オブジェクトの値はラムダ式が実行されるときにすでに決定されていることです。パラメータの値は、ラムダ式が実行されるまで決定されません。栗をとってみましょう:

void fun(){int i=1;int j=2;funcA(i, [j](int k){return k>j;});}

プログラムがfuncAを実行するとき、jの値は決定されますが、kの値は不確定です。kの値が決定されるのは、funcA関数内でラムダ式が実際に呼び出されたときだけです。このプログラムは、もっと明確な別の書き方ができたはずです。

void fun(){int i=1;int j=2;function<bool(int)> f=[j](int k)(return k>j;);funcA(i, f);}

プログラムが実行されてfオブジェクトが生成されると、jの値は決定論的であり、kの値は不確定です。ラムダ式は特殊なデータ型であり、コンパイラはコンパイル時に書かれたラムダ式に基づいてデータ型を自動的に生成することに注意してください。そのため、fオブジェクトを宣言するときには、それがどのような型であるかわからないため、一般的にはauto f=[j]しか使えません。しかし、コールバック関数の型は呼び出し元が宣言されたときに決まります。つまり、ここにはちょっとした矛盾があるのです。この矛盾は、別のライブラリのテンプレート関数型funcionによって解決する必要があります。これを使えば、引数と戻り値の型が同じであれば、同じ関数型を定義することができます。

ラムダ式:

ラムダは匿名関数であり、コードをデータのように受け渡し、より簡潔で柔軟なコードを書くことを可能にする、受け渡し可能なコードの一部と理解することができます。よりコンパクトなコードのスタイルとして、Javaの言語を表現力豊かなものにします。

匿名内部クラスを使用してスレッドを作成する方法は次のとおりです。

Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("run ... ");}};Thread thread = new Thread(runnable);thread.start();

上のコードでは、Runnableインターフェイスを使って直接オブジェクトを生成し、6行のコードを使用しています。

System.out.println("run ... ");

次に、ラムダ式を使ってこのコードを単純化します。

Runnable runnable = ()-> System.out.println("run ...");Thread thread = new Thread(runnable);thread.start();

最小限のコードで同じ機能を実現

ラムダ式の例

要件:学務システムにおける学生の照会操作

  • ニーズ1:3年生以上の生徒へのアクセス

    コードのアイデアは、上記の学生のコレクションをパラメータとして受け取り、フィルタリングされたメソッドに渡し、条件にマッチする学生のコレクションを返すことです。

    public List filterStudentsByGrade(List students){, List stus = new ArrayList<>{if(s.getGrade() >= 3){stus.add(s;}}return stus;}.

この時点で、要件にある必要な機能は完璧に実装されています!

しかし、日々の開発でプログラマーが最も嫌うことは、要件が変更されることです。

  • ニーズ2:男子学生へのアクセス

しかし、そのような要求が増えてくると、コード内のメソッドはすべて同じで、判定条件だけがメソッドごとに異なり、他の部分は同じで、冗長なコードが大量にあることがわかり、コードの最適化、リファクタリングが必要になりました。

物事のコードの非常に良い最適化---デザインパターンを要約するために、数え切れないほどのピットを介して旅行の無数の前任者

次に、デザインパターンを使って上記のコードを最適化します。

  • シンプルなストラテジーインターフェイスSimpleStrategyを作成し、メソッドoperateにオブジェクトを渡すことができます。

    public interface SimpleStrategy{public boolean operate(T t);}.

この時点で、ちょうどクラスの実装のメソッドでSimpleStrategyを渡す必要がある参照してください、あなたは自由にフィルタリングすることができ、テストコードは次のとおりです:

@Testpublic void getMoreThanThreeGrade(){List<Student> sts = filterStudentByStrategy(students, new SimpleStrategy<Student>() {@Overridepublic boolean operate(Student student) {return student.getGender() == 1;}});}

ポリシーパターンが使用され、匿名内部クラスによって生徒の情報がフィルタリングされますが、コード全体で本当に役に立つコードは次の行だけです。

student.getGender() == 1;

ゴスペル:ラムダ式を使って、反復的で冗長なコードを書くのをやめましょう。

List<Student> sts = filterStudentByStrategy(students,(s)->s.getGender() == 1);

この時点で、最終的なフィルターコードはたった1行ですが、本当に完璧なのでしょうか?必ずしもそうではありません!

上記でも、ポリシーのインターフェイスを作成し、また、メソッドを宣言し、コードはまだたくさんある、方法は、インターフェイスを作成しないでください、フィルタメソッドを宣言する必要はありません、直接コードの行で、上記の関数を達成するために?

もちろんあります。ラムダ式とストリームAPIを使うだけで、上記の機能を完璧に実現できます!

List<Student> sts = students.stream().filter(student -> student.getGender() == 1).collect(Collectors.toList());

これは本当に完璧です

**この記事が気に入ったらフォローしてください。

Read next

iOS 14でSwiftUIアプリにAppDelegateを追加するには?

iOS 14では、SwiftUIアプリはAppプロトコルに従いますが、そうではなく、いくつかのシナリオでは、プッシュ登録、メモリ警告の処理など、古いもののライフサイクル関数を使用する必要があります。 ここで ...

Dec 7, 2020 · 1 min read