【Java入門シリーズ】第13章ラムダ式と関数型プログラミング 〜Javaプログラムをシンプルにする技術〜

【Java入門シリーズ】第13章: ラムダ式と関数型プログラミング 〜Javaプログラムをシンプルにする技術〜

Java 8から導入されたラムダ式(Lambda Expression)は、プログラムをより簡潔に、そして効率的に記述できる強力な機能です。従来のJavaでは、匿名クラスやインターフェースを使ったコールバックや処理の委譲が主流でしたが、ラムダ式を使うことで、これらのコードを大幅に短縮し、可読性を向上させることができます。

この章では、Javaのラムダ式を理解するために、以下の内容をカバーします:

  • ラムダ式の基本
  • 関数型インターフェース
  • メソッド参照
  • ストリームAPIとの組み合わせ
  • ラムダ式のユースケースと実践例

13.1 ラムダ式とは?

ラムダ式(Lambda Expression)は、無名関数(名前のない関数)を表現する手段です。従来、匿名クラスを使用していた箇所をより簡潔に記述でき、コードの可読性が向上します。主に、短い処理を行うメソッドの実装に使われ、Javaの関数型プログラミングの基盤となる要素です。

13.1.1 ラムダ式の基本構文

ラムダ式の基本的な構文は次の通りです。

(引数リスト) -> { 実行する処理 }
  • 引数リスト: メソッドのパラメータと同様に、ラムダ式で処理する値を渡します。引数が1つの場合、カッコ()は省略可能です。
  • ->演算子: ラムダ式において、引数と実行する処理を区切る演算子です。
  • 実行する処理: returnを使う必要がない場合、波括弧 {} を省略できます。

13.1.2 ラムダ式の例

具体的な例として、2つの整数を足し合わせるラムダ式を見てみましょう。

// 2つの引数を持ち、その和を返すラムダ式
(int a, int b) -> { return a + b; }

上記のラムダ式は、次のように書き換えて、より簡潔に表現することが可能です。

// 引数の型は推論できるため省略可能
(a, b) -> a + b

13.1.3 従来の匿名クラスとラムダ式の比較

ラムダ式は、従来の匿名クラスの書き方を簡潔に置き換えることができます。例えば、以下の匿名クラスを使った例と、そのラムダ式での表現を比較してみます。

匿名クラスの例:

// Runnableインターフェースを匿名クラスで実装
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("従来の匿名クラスの例");
}
};
new Thread(runnable).start();

ラムダ式での例:

// Runnableインターフェースをラムダ式で実装
Runnable runnable = () -> System.out.println("ラムダ式の例");
new Thread(runnable).start();

ラムダ式を使うことで、コードが非常に簡潔になり、匿名クラスよりも可読性が向上します。


13.2 関数型インターフェース

ラムダ式を使用するためには、関数型インターフェース(Functional Interface)が必要です。関数型インターフェースとは、1つの抽象メソッドのみを持つインターフェースのことです。ラムダ式は、この1つの抽象メソッドを実装する形で利用されます。

13.2.1 関数型インターフェースの例

次に、1つの抽象メソッドだけを持つインターフェースCalculatorを定義し、それをラムダ式で実装してみます。

// 関数型インターフェースの定義
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}

public class Main {
public static void main(String[] args) {
// 2つの数値の足し算を行うラムダ式
Calculator adder = (a, b) -> a + b;
System.out.println(adder.calculate(10, 20)); // 30
}
}

この例では、Calculatorインターフェースにラムダ式を使用して計算処理を実装しています。

13.2.2 @FunctionalInterfaceアノテーション

Java 8では、関数型インターフェースとして定義されたインターフェースに対して、@FunctionalInterfaceアノテーションを付けることができます。これにより、1つの抽象メソッドしか持たないことが保証され、誤って複数の抽象メソッドを定義した場合にコンパイルエラーを発生させます。

@FunctionalInterface
interface MyFunction {
void apply();
}

13.3 Java標準の関数型インターフェース

Java 8では、ラムダ式をより便利に使うために、いくつかの標準の関数型インターフェースが提供されています。これらはjava.util.functionパッケージに含まれており、最もよく使われる関数型インターフェースには以下があります:

13.3.1 Predicateインターフェース

Predicate<T>は、条件を評価してtrueまたはfalseを返す関数型インターフェースです。

import java.util.function.Predicate;

public class Main {
public static void main(String[] args) {
// 数値が10より大きいかを判定するPredicate
Predicate<Integer> isGreaterThanTen = x -> x > 10;

System.out.println(isGreaterThanTen.test(5)); // false
System.out.println(isGreaterThanTen.test(15)); // true
}
}

13.3.2 Functionインターフェース

Function<T, R>は、入力を受け取って処理を行い、結果を返す関数型インターフェースです。

import java.util.function.Function;

public class Main {
public static void main(String[] args) {
// 整数を受け取り、その2倍を返すFunction
Function<Integer, Integer> doubleValue = x -> x * 2;

System.out.println(doubleValue.apply(10)); // 20
}
}

13.3.3 Consumerインターフェース

Consumer<T>は、引数を受け取って処理を行うが、結果を返さない関数型インターフェースです。

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
// 文字列を表示するConsumer
Consumer<String> printer = message -> System.out.println(message);

printer.accept("Javaのラムダ式を学ぼう"); // "Javaのラムダ式を学ぼう"が出力される
}
}

13.3.4 Supplierインターフェース

Supplier<T>は、引数を取らずに結果を返す関数型インターフェースです。

import java.util.function.Supplier;

public class Main {
public static void main(String[] args) {
// 現在の時刻を返すSupplier
Supplier<Long> currentTime = () -> System.currentTimeMillis();

System.out.println(currentTime.get());
}
}

13.4 メソッド参照

メソッド参照は、ラムダ式のシンプルな代替として使用される記法です。特定のクラスのメソッドやインスタンスのメソッドを直接参照して利用できます。

メソッド参照の基本構文は以下の通りです。

クラス名::メソッド名

13.4.1 インスタンスメソッドの参照

次の例では、インスタンスメソッドをメソッド参照で使う例です。

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
Consumer<String> printer = System.out::println; // メソッド参照
printer.accept("メソッド参照の例");
}
}

13.4.2 コンストラクタの参照

コンストラクタもメソッド参照で利用できます。

import java.util.function.Supplier;

class Person {
String name;
Person(String name) {
this.name = name;
}
}

public class Main {
public static void main(String[] args) {
Supplier<Person> personFactory = () -> new Person("太郎");
Person person = personFactory.get();
System.out.println(person.name); // "太郎"と出力
}
}

13.5 ラムダ式とストリームAPI

JavaのストリームAPIとラムダ式は非常に強力な組み合わせです。ストリームAPIを使うことで、コレクションのデータを簡潔に操作でき、ラムダ式を使ってフィルタリング、マッピング、ソート、集計などの操作を効率的に行えます。

13.5.1 フィルタリング

ストリームAPIとラムダ式を使って、リストの要素をフィルタリングする例を示します。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("太郎", "花子", "次郎", "二郎");

// 名前が"郎"で終わるものをフィルタリング
names.stream()
.filter(name -> name.endsWith("郎"))
.forEach(System.out::println); // "太郎", "次郎" が出力される
}
}

13.5.2 ソート

ストリームAPIとラムダ式を使って、リストの要素をソートする例です。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

// 昇順にソート
numbers.stream()
.sorted()
.forEach(System.out::println); // 1, 3, 5, 7, 9 が出力される
}
}

13.5.3 集計

Streamを使って、数値のリストを集計する例を示します。

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 9, 1, 7);

// 合計を計算
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();

System.out.println("合計: " + sum); // 25
}
}

13.6 ラムダ式と関数型プログラミングのまとめ

この章では、Javaのラムダ式関数型プログラミングについて学びました。ラムダ式は、無名関数をシンプルに表現できる便利な構文であり、コードの簡潔化と可読性の向上を実現します。関数型インターフェースと組み合わせることで、ラムダ式を使って柔軟なプログラムを設計することが可能です。

また、メソッド参照やJava標準の関数型インターフェース、ストリームAPIとの連携により、より高度な操作も簡単に行えます。これらの技術を活用することで、コードの冗長性を減らし、より直感的で効率的なプログラムを作成できるようになります。