フィルタは「欲しいものだけを残して、あとは流す」技
フィルタは、ざっくり言うと
「条件に合う要素だけを残して、それ以外を捨てる」処理です。
注文一覧から「未出荷のものだけ」を残す。
ユーザー一覧から「有効ユーザーだけ」を残す。
数値一覧から「0 以上のものだけ」を残す。
業務コードでは、この「フィルタ」がとにかく頻出します。
だからこそ、for 文+if で毎回手書きするのではなく、
“条件を名前付きで表現できるフィルタユーティリティ”として扱えるようになると、一気にコードが読みやすくなります。
一番基本:Stream の filter で絞り込む
例:0 以上の数値だけを残す
まずは、List<Integer> を条件で絞り込む一番シンプルな例から。
import java.util.List;
import java.util.stream.Collectors;
public class FilterBasic {
public static void main(String[] args) {
List<Integer> values = List.of(-1, 0, 10, -5, 3);
List<Integer> nonNegative =
values.stream()
.filter(v -> v >= 0)
.collect(Collectors.toList());
System.out.println(nonNegative); // [0, 10, 3]
}
}
Javaここで押さえてほしい重要ポイントは二つです。
一つ目は、filter(v -> v >= 0) が「0 以上のものだけを通す」という条件をそのまま表していることです。v -> v >= 0 の部分が「フィルタ条件(Predicate)」です。
二つ目は、filter の結果は「新しい Stream」であり、元の values は一切変更されないことです。
「元の一覧はそのまま」「条件に合うものだけを集めた新しい一覧を作る」というのが基本の考え方です。
フィルタ処理をユーティリティメソッドにまとめる
「null や空 List の扱い」を一箇所に閉じ込める
同じようなフィルタ処理を何度も書くなら、
ユーティリティにしてしまうとスッキリします。
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class Filters {
private Filters() {}
public static <T> List<T> filter(
Collection<T> source,
Predicate<? super T> condition
) {
if (source == null || source.isEmpty()) {
return List.of();
}
return source.stream()
.filter(condition)
.collect(Collectors.toList());
}
}
Java使い方はこうなります。
import java.util.List;
public class FilterUtilSample {
public static void main(String[] args) {
List<Integer> values = List.of(-1, 0, 10, -5, 3);
List<Integer> nonNegative =
Filters.filter(values, v -> v >= 0);
System.out.println(nonNegative); // [0, 10, 3]
}
}
Javaここでの重要ポイントは、
「Predicate<? super T> condition が“何を残したいか”を表している」ことです。
Filters.filter(values, v -> v >= 0) と書けば、
「0 以上の値だけを残しているんだな」と一目で分かります。
条件をラムダで渡せるので、どんな業務ルールにも対応できます。
オブジェクト一覧のフィルタ:業務ルールをそのまま書く
例:ユーザー一覧から「有効ユーザーだけ」を残す
業務では、オブジェクトのフィールドに基づいてフィルタすることがほとんどです。
class User {
private final String name;
private final boolean active;
private final String role;
public User(String name, boolean active, String role) {
this.name = name;
this.active = active;
this.role = role;
}
public String getName() { return name; }
public boolean isActive() { return active; }
public String getRole() { return role; }
}
Javaこれを使って、「有効な管理者ユーザーだけ」を残してみます。
import java.util.List;
public class UserFilterSample {
public static void main(String[] args) {
List<User> users = List.of(
new User("山田", true, "ADMIN"),
new User("佐藤", false, "ADMIN"),
new User("鈴木", true, "USER")
);
List<User> activeAdmins =
Filters.filter(users,
u -> u.isActive() && "ADMIN".equals(u.getRole()));
System.out.println(activeAdmins.size()); // 1
}
}
Javaここで深掘りしたい重要ポイントは三つです。
一つ目は、「業務ルール(有効かつ ADMIN)が Predicate としてそのまま書かれている」ことです。u -> u.isActive() && "ADMIN".equals(u.getRole()) が、
「有効な管理者ユーザーだけを残す」という仕様を表現しています。
二つ目は、「フィルタ条件をラムダとして渡すことで、Filters.filter 自体は汎用的なままにできている」ことです。
ユーティリティは「フィルタする」という動作だけを担当し、
「どんな条件か」は呼び出し側が決めます。
三つ目は、「戻り値が新しい List であり、元の users は変更されない」ことです。
副作用がないので、後から読んでも安心して追えるコードになります。
よく使う条件は「名前付き Predicate」にして再利用する
条件に名前をつけると、コードが一気に読めるようになる
ラムダをその場で書いてもいいのですが、
何度も出てくる条件は「名前付きの Predicate」として切り出すと、
コードの意図がさらに分かりやすくなります。
import java.util.function.Predicate;
public final class UserPredicates {
private UserPredicates() {}
public static final Predicate<User> IS_ACTIVE =
User::isActive;
public static final Predicate<User> IS_ADMIN =
u -> "ADMIN".equals(u.getRole());
public static final Predicate<User> IS_ACTIVE_ADMIN =
IS_ACTIVE.and(IS_ADMIN);
}
Java使い方はこうです。
List<User> activeAdmins =
Filters.filter(users, UserPredicates.IS_ACTIVE_ADMIN);
Javaここでの重要ポイントは、
「IS_ACTIVE_ADMIN という名前だけで“有効な管理者ユーザー”という意味が伝わる」ことです。
ラムダを直接書くよりも、
「業務上の概念(有効ユーザー・管理者ユーザー)」に名前を与えることで、
コードが仕様書に近づいていきます。
null を含むデータのフィルタ
「null を除外する」フィルタは超よく使う
現実のデータには null が混ざります。
フィルタで一番よく使うのが、「null を除外する」パターンです。
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public final class Filters {
private Filters() {}
public static <T> List<T> nonNull(Collection<T> source) {
if (source == null || source.isEmpty()) {
return List.of();
}
return source.stream()
.filter(v -> v != null)
.collect(Collectors.toList());
}
}
Java使い方はこうです。
import java.util.List;
public class NonNullFilterSample {
public static void main(String[] args) {
List<String> names = List.of("山田", null, "佐藤");
List<String> nonNullNames = Filters.nonNull(names);
System.out.println(nonNullNames); // [山田, 佐藤]
}
}
Javaここでの重要ポイントは、
「null 除外も“フィルタ条件の一つ”としてユーティリティ化しておくと便利」ということです。
Filters.nonNull を通してから、さらに別の条件でフィルタする、
という二段構えにすると、null 安全なコードを書きやすくなります。
フィルタと他の処理の組み合わせ方
フィルタ → ソート → マッピング → 集計、という流れを意識する
フィルタは、単体で使うよりも、
ソート・マッピング・集計などと組み合わせて使うことが多いです。
例えば、「有効なユーザーだけを名前順に並べて、名前一覧を作る」ならこうです。
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
List<String> activeUserNames =
users.stream()
.filter(User::isActive) // フィルタ
.sorted(Comparator.comparing(User::getName)) // ソート
.map(User::getName) // マッピング
.collect(Collectors.toList()); // List に変換
Javaここでの重要ポイントは、
「フィルタは“最初にノイズを落とす”役割を担うことが多い」という感覚です。
まずフィルタで「対象外」を落とし、
残ったものに対してソート・変換・集計を行う。
この流れを意識すると、処理の意図がとても読みやすくなります。
まとめ:フィルタユーティリティで身につけてほしい感覚
フィルタは、
単に「条件で絞り込むテクニック」ではなく、
「業務ルールを“残す/捨てる”という形でコードに刻む作業」です。
stream().filter(条件) の基本形に慣れる。Filters.filter(コレクション, 条件) のようなユーティリティにして、「何を残したいか」をラムダで表現する。
よく使う条件は UserPredicates.IS_ACTIVE のように名前付き Predicate にして、仕様をそのままコードにする。
null 除外などの共通パターンは専用メソッド(Filters.nonNull など)にしておく。
フィルタを「最初にノイズを落とす処理」として位置づけ、ソート・マッピング・集計と組み合わせて流れで考える。
あなたのコードのどこかに、
同じような if 文で「条件に合うものだけ新しい List に詰め直している」箇所があれば、
それを一度「フィルタユーティリティ+Stream」に置き換えられないか眺めてみてください。
その小さな一歩が、
「“欲しいものだけをきれいに残せる”エンジニア」への、
確かなステップになります。

