Java Tips | コレクション:フィルタ

Java Java
スポンサーリンク

フィルタは「欲しいものだけを残して、あとは流す」技

フィルタは、ざっくり言うと
「条件に合う要素だけを残して、それ以外を捨てる」処理です。

注文一覧から「未出荷のものだけ」を残す。
ユーザー一覧から「有効ユーザーだけ」を残す。
数値一覧から「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」に置き換えられないか眺めてみてください。

その小さな一歩が、
「“欲しいものだけをきれいに残せる”エンジニア」への、
確かなステップになります。

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