PR

Java Stream filter() 完全ガイド:使い方から注意点まで徹底解説

記事内に広告が含まれています。

Java Stream filter()とは?基本を理解しよう

JavaのStream APIは、コレクションや配列の要素を効率的に処理するための強力な機能です。その中でも filter() メソッドは、条件に合う要素だけを抽出するために使われます。たとえば、リストから偶数だけを取り出したい、特定の文字列を含む要素だけに絞り込みたい、といった場面で活躍します。この記事では、filter()の基本的な使い方から、パフォーマンスや注意点まで、実際のコード例を交えて詳しく解説します。これを読めば、あなたもStream filter()を自在に使いこなせるようになるでしょう。

Stream filter()の基本的な構文と使い方

filter()は、Streamインターフェースのメソッドで、引数にPredicate(条件を表す関数)を受け取ります。条件に一致する要素だけを含む新しいStreamを返します。基本的な構文は次のとおりです。

stream.filter(条件)

たとえば、整数のリストから偶数だけを抽出するには、次のように書きます。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
// 結果: [2, 4, 6]

このように、ラムダ式で条件を指定します。条件はbooleanを返すようにします。filter()は中間操作なので、終端操作(collectやforEachなど)を行うまで実際の処理は実行されません。

filter()とmap()の組み合わせでデータを自在に加工

Stream APIでは、filter()で絞り込んだ後にmap()で変換する、という流れが非常に一般的です。たとえば、文字列のリストから「a」を含むものだけを抽出し、大文字に変換するには、次のようにします。

List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> result = words.stream()
    .filter(s -> s.contains("a"))
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 結果: [APPLE, BANANA, DATE]

このように、filter()で不要な要素を除去し、map()で必要な形に変換することで、データ処理を簡潔に記述できます。JavaのStream APIは、このようなパイプライン処理を得意としています。

複数条件を組み合わせる:filter()を連鎖させる方法

filter()は複数回呼び出すことができ、すべての条件を満たす要素だけが残ります。たとえば、年齢が20以上かつ30未満のユーザーを抽出するには、次のように書きます。

List<User> users = ...;
List<User> targetUsers = users.stream()
    .filter(u -> u.getAge() >= 20)
    .filter(u -> u.getAge() < 30)
    .collect(Collectors.toList());

また、1つのfilter()内で&&を使って複数条件を指定することもできます。

.filter(u -> u.getAge() >= 20 && u.getAge() < 30)

どちらの書き方でも結果は同じですが、可読性を考慮して選ぶとよいでしょう。条件が複雑な場合は、メソッド参照や専用のメソッドに切り出すのもおすすめです。

filter()のパフォーマンスと注意点

filter()は便利ですが、パフォーマンスに影響を与える可能性があります。特に、大量のデータを扱う場合や、並列ストリーム(parallelStream)を使う場合は注意が必要です。filter()は各要素に対して条件を評価するため、条件が重い処理(データベースアクセスや複雑な計算)を含むと、処理時間が増大します。また、並列ストリームでは、filter()の条件がスレッドセーフであることが重要です。共有状態を変更するような条件は避け、副作用のない純粋な関数にしましょう。

さらに、filter()は遅延評価されるため、無限ストリームに対しても使えますが、終端操作で結果を確定する必要があります。たとえば、limit()と組み合わせて使うことで、無限ストリームから条件に合う最初のN個を取得できます。

Stream.iterate(0, n -> n + 1)
    .filter(n -> n % 2 == 0)
    .limit(10)
    .forEach(System.out::println);

このコードは、偶数を10個出力します。

filter()の代替手段:anyMatch, allMatch, noneMatch

filter()は要素を抽出するためのものですが、条件を満たす要素が存在するかどうかを確認したい場合は、anyMatch()やallMatch()、noneMatch()を使うと効率的です。これらは短絡操作であり、結果が確定した時点で処理を終了するため、パフォーマンスが向上します。

boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);
boolean noOdd = numbers.stream().noneMatch(n -> n % 2 != 0);

filter()と組み合わせて使うこともありますが、目的に応じて使い分けることが大切です。

実践例:オブジェクトのリストから条件に合うものを抽出

ここでは、実際の開発でよくある例として、ユーザーオブジェクトのリストから特定の条件を満たすユーザーを抽出する方法を紹介します。たとえば、アクティブなユーザーで、かつメールアドレスが「@example.com」で終わるユーザーを抽出するには、次のようにします。

List<User> users = ...;
List<User> filteredUsers = users.stream()
    .filter(User::isActive)
    .filter(u -> u.getEmail().endsWith("@example.com"))
    .collect(Collectors.toList());

このように、メソッド参照を使うとコードが簡潔になります。また、条件が複雑な場合は、専用のPredicateを定義して再利用することもできます。

Predicate<User> isActiveUser = User::isActive;
Predicate<User> hasExampleEmail = u -> u.getEmail().endsWith("@example.com");
List<User> filteredUsers = users.stream()
    .filter(isActiveUser.and(hasExampleEmail))
    .collect(Collectors.toList());

このように、Predicateを組み合わせることで、コードの再利用性が高まります。

filter()と並列ストリームの注意点

並列ストリーム(parallelStream)を使うと、複数のスレッドで処理が実行されるため、大量データの処理が高速化される可能性があります。しかし、filter()を使う際には、条件がスレッドセーフであることが重要です。また、並列処理のオーバーヘッドが大きい場合、かえって遅くなることがあります。データ量が少ない場合や、条件が軽い場合は、通常のストリームを使う方が安全です。

並列ストリームでfilter()を使う例:

List<Integer> numbers = ...;
List<Integer> evenNumbers = numbers.parallelStream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

この場合、filter()の条件は単純な計算なので問題ありませんが、もし条件が共有変数を変更するようなものであれば、並列処理で競合が発生する可能性があります。そのような場合は、条件を純粋な関数にすることが必須です。

filter()の代替:removeIf()との使い分け

ListやSetなどのコレクションには、removeIf()メソッドがあります。これは、条件に合う要素をコレクションから直接削除するためのものです。filter()は新しいコレクションを生成するのに対し、removeIf()は元のコレクションを変更します。

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(n -> n % 2 == 0);
// numbers: [1, 3, 5]

元のリストを変更してもよい場合はremoveIf()を使うと効率的ですが、元のリストを保持したい場合はfilter()を使って新しいリストを作成します。状況に応じて使い分けましょう。

まとめ:Stream filter()をマスターして効率的なJavaプログラミングを

この記事では、JavaのStream filter()メソッドについて、基本的な使い方から応用例、注意点までを解説しました。filter()は、コレクションの要素を条件に基づいて抽出するための非常に便利なメソッドです。map()やcollect()などの他のStream操作と組み合わせることで、複雑なデータ処理を簡潔に記述できます。パフォーマンスや並列処理の注意点を理解し、適切に使いこなすことで、より効率的なJavaプログラミングが可能になります。ぜひ、実際のプロジェクトでfilter()を活用してみてください。

AndroidスマートフォンをAmazonで探す

コメント

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