ZetCode

Java Predicate

上次修改于 2025 年 5 月 28 日

在本文中,我们将展示如何在 Java 中使用谓词。使用谓词,我们可以创建更简洁易读的代码。谓词还有助于创建更好的测试。

谓词定义

谓词 的一般含义是指关于某事物的陈述,该事物要么为真,要么为假。在编程中,谓词表示返回布尔值的单参数函数。

Predicate 接口

在 Java 中,谓词使用函数式接口来实现。 Predicate<T> 接口是一个泛型函数式接口,表示一个返回布尔值的单参数函数。它位于 java.util.function 包中,包含 test(T t) 方法,该方法根据指定的条件评估给定的参数。

与某些编程语言不同,Java 不支持独立函数。此外,Java 中的方法不是一等公民——它们不能存储在集合中或直接作为参数传递。为了支持函数式编程,Java 依赖于接口,允许对象表示函数并传递给诸如 Iterables.filter 之类的方法。借助 Java lambda 表达式,使用谓词变得更加简洁和直观。

Java Predicate 示例

以下示例创建了一个简单的 Java Predicate。

Main.java
class BiggerThanFive<E> implements Predicate<Integer> {

    @Override
    public boolean test(Integer v) {

        Integer five = 5;

        return v > five;
    }
}

void main() {

    List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);

    BiggerThanFive<Integer> btf = new BiggerThanFive<>();
    nums.stream().filter(btf).forEach(System.out::println);
}

在此示例中,谓词用于过滤整数。

class BiggerThanFive<E> implements Predicate<Integer> {

    @Override
    public boolean test(Integer v) {

        Integer five = 5;

        return v > five;
    }
}

这是一个实现 Predicate<Integer> 接口的 Java 类。 其 test 方法对于大于 5 的值返回 true。

List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);

我们有一个整数值列表。

BiggerThanFive<Integer> btf = new BiggerThanFive<>();

实例化一个 BiggerThanFive

nums.stream().filter(btf).forEach(System.out::println);

谓词对象被传递到 filter 方法,以获取列表中所有大于 5 的值。

$ java Main.java
6
7
8
9
12

带有 lambda 的 Java Predicate

Java lambda 表达式简化了 Java Predicate 的创建。

Main.java
void main() {

    List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);

    Predicate<Integer> btf = n -> n > 5;

    nums.stream().filter(btf).forEach(System.out::println);
}

该示例过滤整数值;这次我们使用 Java lambda 表达式,这使得代码更短。

Predicate<Integer> btf = n -> n > 5;

这是一个创建谓词的单行代码。

ArrayList removeIf 方法

ArrayListremoveIf 方法移除所有满足给定谓词的元素。

Main.java
void main() {

    var words = new ArrayList<String>();
    words.add("sky");
    words.add("warm");
    words.add("winter");
    words.add("cloud");
    words.add("pen");
    words.add("den");
    words.add("tree");
    words.add("sun");
    words.add("silk");

    Predicate<String> hasThreeChars = word -> word.length() == 3;
    words.removeIf(hasThreeChars);

    System.out.println(words);
}

我们有一个单词列表。我们从列表中删除所有具有三个拉丁字符的单词。

$ java Main.java
[warm, winter, cloud, tree, silk]

Collectors.PartitioningBy 方法

Collectors.PartitioningBy 返回一个 Collector,它根据 Predicate 对输入元素进行分区,并将它们组织成 Map<Boolean, List<T>>。

Main.java
void main() {

    var values = List.of(3, -1, 2, 4, -1, 1, 2, 3);

    Predicate<Integer> isPositive = e -> e > 0;

    Map<Boolean, List<Integer>> groups = values.stream()
            .collect(Collectors.partitioningBy(isPositive));

    System.out.println(groups.get(true));
    System.out.println(groups.get(false));

    List<List<Integer>> subSets = new ArrayList<>(groups.values());
    System.out.println(subSets);
}

我们有一个整数列表。该列表被划分为两个子列表:正值和负值。

$ java Main.java
[4, 1, 2, 3]
[-3, -1, -2, -1]
[[-3, -1, -2, -1], [4, 1, 2, 3]]

Pattern.asMatchPredicate

Pattern.asMatchPredicate 创建一个谓词,用于测试模式是否与给定的输入字符串匹配。

Main.java
void main() {

    var words = List.of("book", "bookshelf", "bookworm",
            "bookcase", "bookish", "bookkeeper", "booklet", "bookmark");

    var pred = Pattern.compile("book(worm|mark|keeper)?").asMatchPredicate();
    words.stream().filter(pred).forEach(System.out::println);
}

我们使用 Pattern.asMatchPredicate 从正则表达式模式创建谓词,并将其应用于 filter 方法。

$ java Main.java
book
bookworm
bookkeeper
bookmark

Stream.allMatch 谓词

Stream.allMatch 方法返回一个布尔值,指示流的所有元素是否都与提供的谓词匹配。

Main.java
void main() {

    var values1 = List.of(1, 5, 3, 2, 8, 6, 7);
    var values2 = List.of(1, 5, 3, -2, 8, 0, 9);

    Predicate<Integer> isPositive = e -> e > 0;

    var res1 = values1.stream().allMatch(isPositive);

    if (res1) {
        System.out.println("All values of collection values1 are positive");
    } else {
        System.out.println("All values of collection values1 are not positive");
    }

    var res2 = values2.stream().allMatch(isPositive);

    if (res2) {
        System.out.println("All values of collection values2 are positive");
    } else {
        System.out.println("All values of collection values2 are not positive");
    }
}

在该示例中,我们检查两个集合的所有值是否都只有正值。

$ java Main.java
All values of collection values1 are positive
All values of collection values2 are not positive

Pattern.asPredicate

Pattern.asPredicate 方法创建一个谓词,用于测试是否在给定的输入字符串中找到了此模式。 Stream.AnyMatch 方法返回一个布尔值,指示流的任何元素是否与提供的谓词匹配。

Main.java
void main() {

    var words = List.of("skylark", "trial", "water", "cloud", "curtain", "falcon");

    var pred = Pattern.compile("^...{3}$").asPredicate();
    var res = words.stream().anyMatch(pred);

    if (res) {
        System.out.println("There is a word which has three latin characters");
    } else {
        System.out.println("There is no word which has three latin characters");
    }
}

我们有一个单词列表。我们检查是否有一个单词具有三个拉丁字符。

$ java Main.java
There is a word which has three latin characters

Stream.Iterate 方法

Stream.Iterate 方法返回一个顺序有序的流,该流通过将给定的函数迭代地应用于初始元素来生成,并以满足给定的谓词为条件。

Main.java
void main() {

    Predicate<Double> pred = e -> e < 100;
    UnaryOperator<Double> op = e -> e * 2;

    Stream.iterate(1d, pred, op).forEach(System.out::println);
}

使用 Stream.iterate,我们生成一个流,该流将给定的函数应用于先前生成的元素。当谓词返回 false 时,流终止;在我们的例子中,当生成的流值大于 100 时。

$ java Main.java
1.0
2.0
4.0
8.0
16.0
32.0
64.0

具有多个条件的谓词

以下示例使用带有两个条件的谓词。

Main.java
void main() {

    var countries = List.of(
            new Country("Iran", 80840713),
            new Country("Hungary", 9845000),
            new Country("Poland", 38485000),
            new Country("India", 1342512000),
            new Country("Latvia", 1978000),
            new Country("Vietnam", 95261000),
            new Country("Sweden", 9967000),
            new Country("Iceland", 337600),
            new Country("Israel", 8622000));

    Predicate<Country> p1 = c -> c.name().startsWith("I") &&
            c.population() > 10000000;

    countries.stream().filter(p1).forEach(System.out::println);
}

record Country(String name, int population) {
}

在此示例中,我们创建一个国家/地区列表。我们按国家/地区名称和人口过滤该列表。

Predicate<Country> p1 = c -> c.name().startsWith("I") &&
    c.population() > 10000000;

对于以“I”开头的国家/地区且人口超过一千万的国家/地区,该谓词返回 true。

$ java Main.java
Country{name=Iran, population=80840713}
Country{name=India, population=1342512000}

列表中有两个国家/地区满足条件:伊朗和印度。

Predicate.isEqual 方法

Predicate.isEqual 返回一个谓词,该谓词根据 Objects.equals 测试两个参数是否相等。

Main.java
void main() {

    var users1 = List.of(new User("John Doe", "gardener"),
            new User("Roger Roe", "driver"), new User("Jane Doe", "teacher"));

    var users2 = List.of(new User("John Doe", "gardener"),
            new User("Roger Roe", "driver"), new User("Jane Doe", "teacher"));

    var users3 = List.of(new User("John Doe", "architect"),
            new User("Roger Roe", "driver"), new User("Jane Doe", "teacher"));

    Predicate<List<User>> pred = Predicate.isEqual(users1);

    if (pred.test(users2)) {
        System.out.println("users1 and user2 are equal");
    } else {
        System.out.println("users1 and user2 are not equal");
    }

    if (pred.test(users3)) {
        System.out.println("users1 and user3 are equal");
    } else {
        System.out.println("users1 and user3 are not equal");
    }
}

record User(String name, String occupation) {
}

该示例检查两个用户列表是否相等。

$ java Main.java
users1 and user2 are equal
users1 and user3 are not equal

IntPredicate

IntPredicate 表示一个以 int 值作为参数的谓词。 这是 Predicate<E> 的消耗 int 的原始类型特化。

Main.java
void main() {

    int[] nums = { 2, 3, 1, 5, 6, 7, 8, 9, 12 };
    IntPredicate p = n -> n > 5;

    Arrays.stream(nums).filter(p).forEach(System.out::println);
}

该示例使用 filterIntPredicate 过滤 int 值数组。

int nums[] = { 2, 3, 1, 5, 6, 7, 8, 9, 12 };

我们定义一个整数数组。

IntPredicate p = n -> n > 5;

创建一个 IntPredicate;对于大于 5 的 int 值,它返回 true。

Arrays.stream(nums).filter(p).forEach(System.out::println);

我们从数组创建一个流并过滤元素。 filter 方法接收谓词作为参数。

BiPredicate

BiPredicatePredicate 的双元特化。 它表示两个参数的谓词。

Main.java
void main() {

    var words = List.of("sky", "water", "club", "spy", "silk", "summer",
            "war", "cup", "cloud", "coin", "small", "terse", "falcon",
            "snow", "snail", "see");

    BiPredicate<String, Integer> pred = (w, len) -> w.length() == len;
    words.stream().filter(e -> pred.test(e, 3)).forEach(System.out::println);

    System.out.println("---------------------");

    words.stream().filter(e -> pred.test(e, 4)).forEach(System.out::println);
}

BiPredicate 用于挑选具有三个和四个拉丁字符的单词。

$ java Main.java
sky
spy
war
cup
see
---------------------
club
silk
coin
snow

组合谓词

借助 andor 方法,我们可以在 Java 中组合谓词。

Main.java
void main() {

    int[] nums = {2, 3, 1, 5, 6, 7, 8, 9, 12};

    IntPredicate p1 = n -> n > 3;
    IntPredicate p2 = n -> n < 9;

    Arrays.stream(nums).filter(p1.and(p2)).forEach(System.out::println);

    System.out.println("-------------------");

    IntPredicate p3 = n -> n == 6;
    IntPredicate p4 = n -> n == 9;

    Arrays.stream(nums).filter(p3.or(p4)).forEach(System.out::println);
}

该示例使用 IntPredicates 的组合过滤数据。

IntPredicate p1 = n -> n > 3;
IntPredicate p2 = n -> n < 9;

Arrays.stream(nums).filter(p1.and(p2)).forEach(System.out::println);

我们使用 and 方法组合两个谓词;我们得到大于 3 且小于 9 的整数。

IntPredicate p3 = n -> n == 6;
IntPredicate p4 = n -> n == 9;

Arrays.stream(nums).filter(p3.or(p4)).forEach(System.out::println);

使用 or 方法,我们获得等于 6 或 9 的值。

$ java Main.java
5
6
7
8
-------------------
6
9

应用谓词列表

在以下示例中,我们使用谓词列表。

Main.java
void main() {

    var words = List.of("sky", "curtain", "sin", "shy", "way", "club",
            "spy", "silk", "summer", "war", "cup", "cloud", "coin", "small",
            "set", "terse", "tree", "sea", "sip", "snow", "snail", "sly",
            "six", "sod", "see", "sit", "sad", "wry", "why");

    Predicate<String> p1 = e -> e.startsWith("s") || e.startsWith("w");
    Predicate<String> p2 = e -> e.endsWith("y");
    Predicate<String> p3 = e -> e.length() == 3;

    var prs = List.of(p1, p2, p3);

    var result = words.stream()
            .filter(prs.stream().reduce(x -> true, Predicate::and))
            .collect(Collectors.toList());

    result.forEach(System.out::println);
}

借助 reduce 方法,我们将谓词列表应用于单词列表。

$ java Main.java
sky
shy
way
spy
sly
wry
why

具有方法引用的谓词

可以使用方法引用轻松创建谓词。这些是使用 :: 运算符创建的。

Main.java
void main() {

    var words = List.of("sky", "", "club", "spy", "silk", "summer",
            "war", "cup", "cloud", "coin", "small", "terse", "",
            "snow", "snail", "see");

    Predicate<String> pred = String::isEmpty;

    var res = words.stream().anyMatch(pred);

    if (res) {
        System.out.println("There is an empty string");
    } else {
        System.out.println("There is no empty string");
    }
}

该示例检查列表中是否有任何空字符串。

否定谓词

negate 方法返回一个表示给定谓词的逻辑否定的谓词。

Main.java
void main() {

    int[] nums = {2, 3, 1, 5, 6, 7, 8, 9, 12};

    IntPredicate p = n -> n > 5;

    Arrays.stream(nums).filter(p).forEach(System.out::println);

    System.out.println("-----------------");

    Arrays.stream(nums).filter(p.negate()).forEach(System.out::println);
}

该示例演示了 negate 方法的用法。

IntPredicate p = n -> n > 5;

我们有一个对于大于 5 的值返回 true 的谓词。

Arrays.stream(nums).filter(p).forEach(System.out::println);

我们过滤所有大于 5 的整数。

Arrays.stream(nums).filter(p.negate()).forEach(System.out::println);

借助 negate 方法,我们得到相反的结果:小于或等于 4 的值。

$ java Main.java
6
7
8
9
12
-----------------
2
3
1
5

或者,我们可以使用 Predicate.not 方法。

Main.java
void main() {

    var words = List.of("book", "cup", "tree", "town", "sky", "by",
            "call", "ten", "top", "smart", "park");

    Predicate<String> hasThreeChars = (String word) -> word.length() == 3;

    var res = words.stream().filter(hasThreeChars).toList();
    System.out.println(res);

    var res2 = words.stream().filter(Predicate.not(hasThreeChars)).toList();
    System.out.println(res2);
}

在该程序中,我们定义一个单词列表。定义的谓词对于所有包含三个拉丁字符的单词返回 true。 Predicate.not(hasThreeChars) 返回相反的结果:所有具有较少或更多拉丁字符的单词。

$ java Main.java
[cup, sky, ten, top]
[book, tree, town, call, smart, park]

谓词作为方法参数

谓词可以作为方法参数传递。

Main.java
void main() {

    List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7,
            8, 9, 10, 11, 12);

    List<Integer> all = eval(list, n -> true);
    System.out.println(all);

    List<Integer> evenValues = eval(list, n -> n % 2 == 0);
    System.out.println(evenValues);

    List<Integer> greaterThanSix = eval(list, n -> n > 6);
    System.out.println(greaterThanSix);
}

List<Integer> eval(List<Integer> values,
        Predicate<Integer> predicate) {
    return values.stream().filter(predicate)
            .collect(Collectors.toList());
}

在此示例中,我们将谓词函数作为第二个参数传递给 eval 方法。

来源

Java Predicate - 语言参考

在本文中,我们展示了如何在 Java 中使用谓词。 我们演示了如何创建谓词、如何在流中使用它们以及如何组合它们。 我们还展示了如何将谓词与 lambda 表达式和方法引用一起使用。 谓词是 Java 中的一个强大工具,可以帮助我们编写更简洁易读的代码。

作者

我的名字是 Jan Bodnar,我是一位热情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程