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。
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 的创建。
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 方法
ArrayList 的 removeIf 方法移除所有满足给定谓词的元素。
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>>。
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 创建一个谓词,用于测试模式是否与给定的输入字符串匹配。
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 方法返回一个布尔值,指示流的所有元素是否都与提供的谓词匹配。
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 方法返回一个布尔值,指示流的任何元素是否与提供的谓词匹配。
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 方法返回一个顺序有序的流,该流通过将给定的函数迭代地应用于初始元素来生成,并以满足给定的谓词为条件。
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
具有多个条件的谓词
以下示例使用带有两个条件的谓词。
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 测试两个参数是否相等。
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 的原始类型特化。
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);
}
该示例使用 filter 和 IntPredicate 过滤 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
BiPredicate 是 Predicate 的双元特化。 它表示两个参数的谓词。
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
组合谓词
借助 and 和 or 方法,我们可以在 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
应用谓词列表
在以下示例中,我们使用谓词列表。
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
具有方法引用的谓词
可以使用方法引用轻松创建谓词。这些是使用 :: 运算符创建的。
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 方法返回一个表示给定谓词的逻辑否定的谓词。
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 方法。
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]
谓词作为方法参数
谓词可以作为方法参数传递。
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 中使用谓词。 我们演示了如何创建谓词、如何在流中使用它们以及如何组合它们。 我们还展示了如何将谓词与 lambda 表达式和方法引用一起使用。 谓词是 Java 中的一个强大工具,可以帮助我们编写更简洁易读的代码。
作者
列出所有Java教程。