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
}
}
JavanotNull.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;
JavaString::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 には「プリミティブ専用」のバリエーションが用意されています。
IntPredicate:int -> booleanLongPredicate:long -> booleanDoublePredicate:double -> 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
}
}
JavaStream まわりでも、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などのバリエーションもある
というあたりです。
