4-1. ラムダ式¶
ラムダ式(Lambda Expression)は、Java 8で導入された関数型プログラミングの要素で、コードを簡潔に記述できます。
ラムダ式とは¶
ラムダ式は、匿名関数(名前のない関数)を簡潔に記述するための構文です。
従来の書き方 vs ラムダ式¶
// 従来の匿名クラス
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// ラムダ式
Runnable runnable2 = () -> System.out.println("Hello");
ラムダ式の構文¶
基本形¶
パターン¶
// パラメータなし
() -> System.out.println("Hello");
// パラメータ1つ(型推論、括弧省略可能)
x -> x * 2
(x) -> x * 2
(int x) -> x * 2
// パラメータ複数
(x, y) -> x + y
(int x, int y) -> x + y
// 複数行の処理
(x, y) -> {
int sum = x + y;
return sum * 2;
}
関数型インターフェース¶
ラムダ式は、関数型インターフェース(抽象メソッドが1つだけのインターフェース)の実装として使用されます。
主要な関数型インターフェース¶
Predicate(条件判定)¶
import java.util.function.Predicate;
Predicate<Integer> isPositive = x -> x > 0;
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-3)); // false
// 実用例
List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
numbers.stream()
.filter(x -> x > 0)
.forEach(System.out::println); // 1, 3, 5
Function(変換)¶
import java.util.function.Function;
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Hello")); // 5
// 実用例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.map(s -> s.toUpperCase())
.forEach(System.out::println); // ALICE, BOB, CHARLIE
Consumer(消費)¶
import java.util.function.Consumer;
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello"); // Hello
// 実用例
List<String> items = Arrays.asList("A", "B", "C");
items.forEach(item -> System.out.println(item));
Supplier(供給)¶
import java.util.function.Supplier;
Supplier<Double> random = () -> Math.random();
System.out.println(random.get()); // ランダムな値
// 実用例
Supplier<String> greeting = () -> "Hello, World!";
System.out.println(greeting.get());
BiFunction(2つの引数を持つ変換)¶
import java.util.function.BiFunction;
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
System.out.println(add.apply(3, 5)); // 8
カスタム関数型インターフェース¶
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
// 使用例
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
System.out.println(add.calculate(3, 5)); // 8
System.out.println(multiply.calculate(3, 5)); // 15
@FunctionalInterface: 関数型インターフェースであることを明示(省略可能だが推奨)
メソッド参照¶
既存のメソッドをラムダ式のように使用できます。
種類¶
// 静的メソッド参照
Function<String, Integer> parse1 = Integer::parseInt;
// 同等: s -> Integer.parseInt(s)
// インスタンスメソッド参照
String str = "Hello";
Supplier<Integer> length1 = str::length;
// 同等: () -> str.length()
// 任意のオブジェクトのインスタンスメソッド参照
Function<String, Integer> length2 = String::length;
// 同等: s -> s.length()
// コンストラクタ参照
Supplier<List<String>> listSupplier = ArrayList::new;
// 同等: () -> new ArrayList<>()
実用例¶
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// ラムダ式
names.forEach(name -> System.out.println(name));
// メソッド参照
names.forEach(System.out::println);
// ラムダ式
names.stream()
.map(name -> name.toUpperCase())
.forEach(System.out::println);
// メソッド参照
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
実践例: コレクションの操作¶
リストのフィルタリングとソート¶
import java.util.*;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class LambdaExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 20),
new Person("David", 35)
);
// 従来の方法
Collections.sort(people, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return Integer.compare(p1.getAge(), p2.getAge());
}
});
// ラムダ式
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
// メソッド参照とComparator
people.sort(Comparator.comparing(Person::getAge));
// 逆順
people.sort(Comparator.comparing(Person::getAge).reversed());
// 複数の条件
people.sort(Comparator.comparing(Person::getAge)
.thenComparing(Person::getName));
people.forEach(System.out::println);
}
}
Comparatorの便利なメソッド¶
List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");
// 自然順序
names.sort(Comparator.naturalOrder());
// 逆順
names.sort(Comparator.reverseOrder());
// 大文字小文字を無視
names.sort(String.CASE_INSENSITIVE_ORDER);
// 長さでソート
names.sort(Comparator.comparing(String::length));
// 複数条件
List<Person> people = ...;
people.sort(
Comparator.comparing(Person::getAge)
.thenComparing(Person::getName)
);
実践例: イベントハンドラ(概念)¶
ラムダ式は、GUIやイベント駆動プログラミングでも活用されます。
// 従来の方法
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
// ラムダ式
button.addActionListener(e -> System.out.println("Button clicked"));
実践例: カスタムフィルタ¶
import java.util.*;
import java.util.function.Predicate;
public class FilterExample {
// 汎用フィルタメソッド
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 偶数のみ
List<Integer> evens = filter(numbers, n -> n % 2 == 0);
System.out.println(evens); // [2, 4, 6, 8, 10]
// 5より大きい
List<Integer> greaterThan5 = filter(numbers, n -> n > 5);
System.out.println(greaterThan5); // [6, 7, 8, 9, 10]
// 文字列にも適用
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> longNames = filter(names, s -> s.length() > 4);
System.out.println(longNames); // [Alice, Charlie, David]
}
}
変数のキャプチャ¶
ラムダ式は、外側のスコープの変数を参照できます(クロージャ)。
int factor = 2; // 実質的にfinal
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(n -> System.out.println(n * factor));
// factor = 3; // エラー: キャプチャされた変数は変更不可
注意: キャプチャされる変数は、実質的にfinal(変更されない)でなければなりません。
ラムダ式のベストプラクティス¶
1. 簡潔さを保つ¶
// 良い例(簡潔)
list.forEach(item -> System.out.println(item));
// 避けるべき(冗長)
list.forEach(item -> {
System.out.println(item);
});
2. メソッド参照を活用¶
3. 複雑なロジックは別メソッドに¶
// 避けるべき(長すぎるラムダ式)
list.stream()
.filter(item -> {
// 複雑な処理が続く...
return someComplexCondition;
});
// 良い例
list.stream()
.filter(this::isValid);
private boolean isValid(Item item) {
// 複雑な処理
return someComplexCondition;
}
4. 型推論を活用¶
// 良い例
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// 冗長
BiFunction<Integer, Integer, Integer> add = (Integer a, Integer b) -> a + b;
まとめ¶
- ラムダ式: 匿名関数を簡潔に記述
(params) -> expression - 関数型インターフェース: 抽象メソッドが1つのインターフェース
Predicate<T>: 条件判定Function<T, R>: 変換Consumer<T>: 消費Supplier<T>: 供給- メソッド参照: 既存メソッドをラムダ式のように使用
Class::staticMethodinstance::methodClass::instanceMethodClass::new- 利点:
- コードの簡潔化
- 関数型プログラミングの要素
- コレクション操作の柔軟性
次のセクションでは、Stream APIについて学びます。