Java | Java 詳細・モダン文法:ラムダ式・関数型 – Predicate

Java Java
スポンサーリンク

Predicate を一言でいうと

java.util.function.Predicate<T> は、

「T 型の値を 1 つ受け取って、true / false を返す“条件判定関数”を表す関数型インターフェース」
です。

形だけ書くと、こういう関数の“型”です。

T -> boolean
Java

つまり、

  • 「このユーザーは大人か?」
  • 「この文字列は空ではないか?」
  • 「この数は正の数か?」

のような、“はい・いいえ”で答えられる条件チェックを、
ラムダ式で表現するときのためのインターフェースです。


Predicate の基本形とシグネチャ

インターフェースの中身を見てみる

Predicate<T> の定義(ざっくり)はこうなっています。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    // ほかに and, or, negate などの default メソッドがある
}
Java

超大事なのはこの 1 行です。

boolean test(T t);
Java

つまり、

  • 引数:T t
  • 戻り値:boolean

という 1 メソッドだけを持つ関数型インターフェースです。

これを頭の中で「T -> boolean」とイメージできるようにしてください。

一番シンプルな使用例

例として、「文字列が空でないか」を判定する Predicate<String> を作ってみます。

import java.util.function.Predicate;

public class PredicateBasic {
    public static void main(String[] args) {
        Predicate<String> notEmpty = s -> s != null && !s.isEmpty();

        System.out.println(notEmpty.test("hello")); // true
        System.out.println(notEmpty.test(""));      // false
        System.out.println(notEmpty.test(null));    // false
    }
}
Java

ここでのポイントは、

  • Predicate<String> が「String -> boolean」の関数の型
  • s -> s != null && !s.isEmpty() が、その実装(ラムダ式)
  • test() で判定結果を取得

という構造になっていることです。


コレクションのフィルタと Predicate

Stream API の filter との組み合わせ(超よく使う)

Predicate が一番輝くのは、「フィルタする条件」をラムダで渡す場面です。

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateFilterExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "", null);

        Predicate<String> notEmpty = s -> s != null && !s.isEmpty();

        List<String> filtered =
                names.stream()
                     .filter(notEmpty)      // Predicate<String> を渡している
                     .collect(Collectors.toList());

        System.out.println(filtered);      // [Alice, Bob, Charlie]
    }
}
Java

ここで filter のシグネチャはこうです。

Stream<T> filter(Predicate<? super T> predicate)
Java

つまり、

「要素 T を 1 つ受け取って、残すなら true、捨てるなら false を返す関数」

を渡すことで、
true の要素だけが残った新しい Stream を作る、というイメージです。

filter(x -> 条件) と、無名ラムダで書いてしまうことも多いですが、
Predicate として変数に切り出して名前を付けることで、「何を判定しているのか」が分かりやすくなります。


and / or / negate による条件の合成(ここが重要)

条件を積み重ねていくイメージ

Predicate は、ただ test するだけでなく、
複数の条件を組み合わせるための便利メソッドを持っています。

  • and(かつ)
  • or(または)
  • negate(否定)

これが使えるようになると、「if 文だらけの条件」を“組み合わせ可能な部品”として扱えるようになります。

and:両方 true なら true

例:
「非 null で」「空でない」文字列だけを OK にしたい。

import java.util.function.Predicate;

public class PredicateAndExample {
    public static void main(String[] args) {
        Predicate<String> notNull   = s -> s != null;
        Predicate<String> notEmpty  = s -> !s.isEmpty();

        Predicate<String> valid =
                notNull.and(notEmpty);   // どちらも true なら true

        System.out.println(valid.test("hello")); // true
        System.out.println(valid.test(""));      // false
        System.out.println(valid.test(null));    // false
    }
}
Java

notNull.and(notEmpty) は、

s -> notNull.test(s) && notEmpty.test(s)
Java

と同じ意味です。

この「条件を名前付きの部品として持っておき、あとで合成する」感覚が、
関数型っぽい書き方の入り口になります。

or:どちらか true なら true

例:
「空文字 or null は OK」としたい(逆に“空か null だけ true”)。

Predicate<String> isNull   = s -> s == null;
Predicate<String> isEmpty  = s -> s != null && s.isEmpty();

Predicate<String> nullOrEmpty =
        isNull.or(isEmpty);
Java

これは、

s -> isNull.test(s) || isEmpty.test(s)
Java

と同じ意味です。

negate:true/false をひっくり返す

negate() はその名の通り、「判定結果を反転」します。

Predicate<String> notEmpty   = s -> s != null && !s.isEmpty();
Predicate<String> isEmptyOrNull = notEmpty.negate();
Java

これは、実質こういう感じです。

s -> !notEmpty.test(s)
Java

つまり notEmpty の逆の条件になっています。

negate を使うと、「否定条件」も読みやすく書けます。


メソッド参照と Predicate

既存メソッドをそのまま条件として使う

ラムダ式の代わりに「メソッド参照」を使うと、さらに読みやすくなる場合があります。

例:
「文字列が空かどうか」を判定する Predicate<String> を作りたい。

ラムダ式で書くと:

Predicate<String> isEmpty = s -> s.isEmpty();
Java

メソッド参照で書くと:

Predicate<String> isEmpty = String::isEmpty;
Java

String::isEmpty は、「String 型のインスタンスメソッド isEmpty() を、そのまま Predicate<String> として使う」という意味です。

Stream での利用例:

List<String> names = List.of("Alice", "", "Bob");

long emptyCount = names.stream()
                       .filter(String::isEmpty)  // isEmpty が true のものだけ残る
                       .count();
Java

「ラムダで書いたらどうなるか」を考えると理解しやすいです。

.filter(s -> s.isEmpty())
Java

これは Predicate<String> です。
String::isEmpty は、そのラムダのもっと短い書き方だと思ってください。


原始型用の Predicate(IntPredicate など)

オートボクシングのオーバーヘッドを避けるバリエーション

Predicate<T> は「T 型」のための関数型インターフェースなので、
Predicate<Integer> のように書くと、「int → Integer のボックス化」が発生します。

数値を大量に判定するような場面では、
java.util.function には「プリミティブ専用」のバリエーションが用意されています。

  • IntPredicateint -> boolean
  • LongPredicatelong -> boolean
  • DoublePredicatedouble -> boolean

定義のイメージはこんな感じです。

@FunctionalInterface
public interface IntPredicate {
    boolean test(int value);
}
Java

使い方は Predicate<Integer> とほぼ同じです。

import java.util.function.IntPredicate;

public class IntPredicateExample {
    public static void main(String[] args) {
        IntPredicate isEven = x -> x % 2 == 0;

        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(5)); // false
    }
}
Java

Stream まわりでも、IntStream などと組み合わせて使われます。


自分で Predicate を使ってみる:実用寄りの例

ユーザのリストを複数の条件でフィルタする

簡単な User クラスを想定します。

class User {
    final String name;
    final int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}
Java

「成人かどうか」「名前が特定の文字で始まるか」を条件にフィルタしてみます。

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class UserFilterExample {
    public static void main(String[] args) {
        List<User> users = List.of(
                new User("Alice", 20),
                new User("Bob", 17),
                new User("Charlie", 25),
                new User("Ann", 30)
        );

        Predicate<User> isAdult = u -> u.age >= 20;
        Predicate<User> nameStartsWithA = u -> u.name.startsWith("A");

        Predicate<User> adultAndNameStartsWithA =
                isAdult.and(nameStartsWithA);

        List<User> filtered =
                users.stream()
                     .filter(adultAndNameStartsWithA)
                     .collect(Collectors.toList());

        System.out.println(filtered); // [Alice(20), Ann(30)]
    }
}
Java

ここでやっていることは、

  • 条件を小さな Predicate<User> として分割
  • and で組み合わせて「複合条件」を作る
  • filter に渡して、コレクションをフィルタ

という流れです。

if 文をネストしまくる代わりに、
条件を「部品」として扱い、組み立て直せるようになっているのが分かると思います。


まとめ:Predicate を自分の中でこう位置づける

Predicate<T> を一文でまとめると、

「T を 1 つ受け取って boolean を返す、“条件” を表現するための関数型インターフェース」
です。

特に重要なのは、

  • メソッドシグネチャは boolean test(T t)
  • Stream の filter などで、「残す要素の条件」として多用される
  • and, or, negate で条件を組み合わせられる(ここはしっかり使えるようにしたい)
  • メソッド参照(String::isEmpty など)とも相性がいい
  • 数値用に IntPredicate などのバリエーションもある

というあたりです。

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