Javaラムダ式:コードをもっとスッキリ書こう!

docs

前回は、コレクションの要素を順番に処理するのに便利なイテレータについて学んだよね。コレクションの中身をぐるぐる回して、一つずつ要素を取り出す方法がわかったはず。

Javaイテレータ:コレクションの要素を順番に処理しよう! | ToolDocs

Javaのプログラミングを進めていくと、「処理の塊」をどこかに渡したり、一時的に作ったりする場面によく出くわすんだ。例えば、コレクションの要素を特定の条件で並び替えたいとか、あるイベントが発生したときに実行される処理を定義したいとかね。

そんなときに、Java 8から導入されたとっても便利な機能がラムダ式なんだ! これを使うと、今までちょっと面倒だったコードが驚くほどスッキリ書けるようになるんだよ。

ラムダ式ってなに?

ラムダ式を一言で言うと、**「名前のない短いメソッド」**みたいなものだよ。通常、メソッドを作るにはクラスの中に宣言して、名前を付けて…といった手順が必要だよね。でも、ラムダ式を使えば、もっと手軽に、必要な場所に直接「この処理を実行してね」と書けるようになるんだ。

なんでこんなものが必要になったんだろう? それは、Javaが関数型プログラミングという考え方を取り入れるようになったからなんだ。難しく考える必要はなくて、要は「処理そのもの」をデータのように扱えるようにしたい、ということなんだ。

なぜラムダ式が必要なの? 匿名クラスとの比較

ラムダ式が登場する前は、一時的に「処理の塊」を定義したい場合、**匿名クラス(無名クラス)**を使うのが一般的だったんだ。匿名クラスは、その場で一時的に作成されるクラスで、特定のインターフェースを実装したり、クラスを継承したりして使われるよ。

例えば、ボタンがクリックされたときに何か処理をしたい場合を考えてみよう。

匿名クラスを使った例:

Java

import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class AnonymousClassExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("匿名クラスの例");
        JButton button = new JButton("クリックしてね!");

        // ボタンがクリックされた時の処理を匿名クラスで定義
        button.addActionListener(new ActionListener() { // ここが匿名クラス!
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("ボタンがクリックされました!");
            }
        });

        frame.setLayout(new FlowLayout());
        frame.add(button);
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

このコード、ボタンをクリックしたときの処理を定義するために、なんだかたくさん書かないといけないよね。new ActionListener() { ... } の部分が匿名クラスだよ。

vscodeで実行した結果

じゃあ、これをラムダ式で書き直してみよう!

ラムダ式を使った例:

Java

import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
// import java.awt.event.ActionListener; // これも不要になる!

public class LambdaExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("ラムダ式の例");
        JButton button = new JButton("クリックしてね!");

        // ボタンがクリックされた時の処理をラムダ式で定義
        button.addActionListener(e -> System.out.println("ボタンがクリックされました!")); // ここがラムダ式!

        frame.setLayout(new FlowLayout());
        frame.add(button);
        frame.setSize(300, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

どうかな? ActionListener を実装するための余計なコードがごっそりなくなって、驚くほどスッキリしたのがわかるよね! これがラムダ式の最大のメリットなんだ。

vscodeで実行した結果

ラムダ式の書き方:基本の3つのパターン

ラムダ式は、基本的には以下の3つの部分から構成されるよ。

  1. 引数リスト: メソッドの引数と同じように、処理に渡されるデータ。
  2. -> (アロー演算子): 引数リストと処理本体を区切る記号。
  3. 処理本体: 実際に実行される処理のコード。

いくつかのパターンを見ていこう!

パターン1:引数が1つで、処理が1行の場合

これが一番シンプルで、さっきのボタンの例もこのパターンだね。

Java

// (引数) -> { 処理 }

// 例1: 引数eを受け取り、それを表示する
// e -> System.out.println(e);

// 例2: 型推論により、引数の型を省略できることが多い
// String text -> System.out.println(text);  // 冗長
// text -> System.out.println(text);        // よりシンプル

引数が1つの場合、引数を囲むカッコ () を省略できることが多いよ。

パターン2:引数が複数、または引数がない場合

引数が複数ある場合や、引数がない場合は、引数リストをカッコ () で囲む必要があるよ。

Java

// (引数1, 引数2, ...) -> { 処理 }
// () -> { 処理 }

// 例3: 2つの引数を受け取って足し算する
// (a, b) -> System.out.println(a + b);

// 例4: 引数なしで、単にメッセージを表示する
// () -> System.out.println("Hello, Lambda!");

パターン3:処理が複数行の場合

処理が複数行になる場合は、処理本体を波カッコ {} で囲む必要があるよ。

Java

// (引数) -> {
//     処理1;
//     処理2;
//     ...
// }

// 例5: 引数を受け取って、複数の処理を行う
// (name) -> {
//     System.out.println("こんにちは、" + name + "さん!");
//     System.out.println("元気ですか?");
// };

パターン4:戻り値がある場合

処理の結果を返す(戻り値がある)場合は、return 文を使うよ。処理が1行で、それが戻り値になる場合は、return とセミコロン ; を省略できるよ!

Java

// (引数) -> 戻り値;
// (引数) -> { return 戻り値; };

// 例6: 2つの数字を足して結果を返す
// (a, b) -> a + b; // return と ; を省略

// 例7: 複数行の処理で結果を返す
// (x, y) -> {
//     int sum = x + y;
//     return sum * 2;
// };

関数型インターフェースってなに?

さて、ラムダ式は「名前のない短いメソッド」だと言ったけど、Javaではどんなメソッドでもラムダ式にできるわけじゃないんだ。ラムダ式が使えるのは、**「関数型インターフェース」**という特別なインターフェースなんだ。

関数型インターフェースとは、抽象メソッドが1つだけ定義されているインターフェースのことだよ。さっきの ActionListener も、実は actionPerformed(ActionEvent e) という抽象メソッドが1つだけ定義されている関数型インターフェースなんだ。

Javaでは、@FunctionalInterface アノテーションを付けて関数型インターフェースであることを明示できるよ(付けなくても抽象メソッドが1つだけなら関数型インターフェースとして扱われるけど、付けておくと間違いを防ぎやすい)。

自分で関数型インターフェースを作ってみよう。

Java

// MyCalculatorという関数型インターフェースを定義
@FunctionalInterface
interface MyCalculator {
    int calculate(int a, int b); // 抽象メソッドが1つだけ
}

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        // MyCalculatorインターフェースをラムダ式で実装
        MyCalculator adder = (x, y) -> x + y; // 足し算のラムダ式
        MyCalculator multiplier = (x, y) -> x * y; // 掛け算のラムダ式

        System.out.println("5 + 3 = " + adder.calculate(5, 3));
        System.out.println("5 * 3 = " + multiplier.calculate(5, 3));
    }
}

実行結果:

5 + 3 = 8
5 * 3 = 15

こんな風に、自分で定義した関数型インターフェースに対してもラムダ式を使えるんだ。とっても柔軟だよね!

vscodeで実行した結果

Javaが用意している便利な関数型インターフェース

Java 8からは、よく使う汎用的な関数型インターフェースが標準でたくさん用意されているよ。代表的なものをいくつか紹介するね。

  • Predicate<T>: 引数を1つ受け取り、boolean を返す (T -> boolean)。条件判定によく使うよ。
  • Consumer<T>: 引数を1つ受け取るが、何も返さない (T -> void)。コレクションの要素を1つずつ処理する際などに使うよ。
  • Function<T, R>: 引数を1つ受け取り、戻り値を返す (T -> R)。入力値を別の型に変換するときなどに使うよ。
  • Supplier<T>: 引数を受け取らず、何かを返す (() -> T)。値を供給するときなどに使うよ。
  • UnaryOperator<T>: 引数を1つ受け取り、同じ型の戻り値を返す (T -> T)。
  • BinaryOperator<T>: 同じ型の引数を2つ受け取り、同じ型の戻り値を返す ((T, T) -> T)。

これらのインターフェースを使いこなせるようになると、コードの幅がぐっと広がるよ!

例として、ListforEach メソッドと Consumer を使ってみよう。

Java

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer; // Consumerインターフェースをインポート

public class ConsumerExample {
    public static void main(String[] args) {
        List<String> greetings = new ArrayList<>();
        greetings.add("おはよう");
        greetings.add("こんにちは");
        greetings.add("こんばんは");

        // Consumerインターフェースのラムダ式を使って各要素を表示
        Consumer<String> printer = message -> System.out.println(message + "!");
        greetings.forEach(printer); // forEachメソッドはConsumerを引数にとる

        System.out.println("\n別のラムダ式で直接記述:");
        // もっと短く、直接ラムダ式を渡すこともできる
        greetings.forEach(message -> System.out.println("私は「" + message + "」と言いました。"));
    }
}

実行結果:

おはよう!
こんにちは!
こんばんは!

別のラムダ式で直接記述:
私は「おはよう」と言いました。
私は「こんにちは」と言いました。
私は「こんばんは」」と言いました。

forEach メソッドは、リストの各要素に対して指定された処理を実行するメソッドで、Consumer 型の引数を受け取るんだ。だから、ラムダ式を直接渡すことができるんだね。とっても便利!

vscodeで実行した結果

メソッド参照ってなに?

ラムダ式をさらに簡潔に書く方法として、メソッド参照というものもあるよ。これは、既存のメソッドをラムダ式の代わりに使うことができる機能なんだ。

例えば、System.out.println() は、引数を1つ受け取って何も返さないメソッドだよね。これは Consumer の形とぴったり合うんだ。

Java

import java.util.ArrayList;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        System.out.println("ラムダ式で表示:");
        names.forEach(name -> System.out.println(name)); // ラムダ式

        System.out.println("\nメソッド参照で表示:");
        names.forEach(System.out::println); // メソッド参照!
    }
}

実行結果:

ラムダ式で表示:
Alice
Bob
Charlie

メソッド参照で表示:
Alice
Bob
Charlie

System.out::println という書き方がメソッド参照だよ。ラムダ式よりもさらに短く、コードが読みやすくなるね! メソッド参照にはいくつか種類があるけど、ここでは「クラス名::スタティックメソッド名」の形だと覚えておこう。

vscodeで実行した結果

まとめ

今回はJavaのラムダ式について学んだよ。

  • ラムダ式は**「名前のない短いメソッド」**で、コードを簡潔に書ける。
  • 匿名クラスの代わりに使うことで、記述量を大幅に減らせる。
  • 引数リスト、->(アロー演算子)、処理本体の3つの部分から構成される。
  • ラムダ式が使えるのは**抽象メソッドが1つだけ定義された「関数型インターフェース」**に対して。
  • Javaには PredicateConsumerFunction など、便利な標準関数型インターフェースがたくさんある。
  • メソッド参照を使えば、既存のメソッドをラムダ式の代わりにさらに簡潔に書ける。

ラムダ式は、Javaプログラミングの効率を劇的に上げてくれる強力な機能だよ。最初は少し難しく感じるかもしれないけど、使っていくうちにその便利さがわかるはず。ぜひ色々な場面で試してみてね!


前回の記事はこちらから → Javaイテレータ:コレクションの要素を順番に処理しよう! | ToolDocs

コメント

タイトルとURLをコピーしました