网站首页 > 基础教程 正文
在之前的文章
我们已经了解了Consumer和BiConsumer函数式接口。在本文中,我们将看到另一个重要的函数式接口,即Predicate.
开始
Predicate表示带有一个参数的函数表达式并返回一个布尔值。
下面是函数式接口Predicate的定义。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t); // 只有一个抽象方法
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
上面的类有一个名为 test(T t) 的抽象方法,它返回一个布尔值。
假设我们需要测试一个整数是偶数还是奇数。我们可以写一个这样的Predicate。
Predicate<Integer> isEven = new Predicate<Integer>() {
@Override
public boolean test(Integer num) {
return num % 2 == 0;
}
};
将上面的内容转换成 lambda 表达式,得到下面这个。
Predicate<Integer> isEven = n -> n % 2 == 0;
这个Predicate怎么用?用在哪里?是我们现在关注的问题。
过滤流中元素
我们的最终目标是计算 int 随机整数流中有多少个偶数。
假设我们有一个随机整数流,如下所示。
Random random = new Random();
Stream<Integer> randIntStream = random
.ints(100, 1, 10000)
.boxed();
上面的 random.ints(100, 1, 10000) 行给出了 100 个整数的流,这些整数在 1 到 10000 之间是随机的。
现在我们只想过滤偶数整数并对它们进行计数。 IntStream 有一个过滤方法,它接受Predicate。
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
Stream<T> filter(Predicate<? super T> predicate);
...
}
现在我们可以调用过滤器方法并提供一个 lambda
long count = randIntStream
.filter(i -> i % 2 == 0)
.count();
我们已将 lambda 表达式作为参数发送到filter方法。本质上lambda表达式 i -> i %2 == 0 是 Predicate<Integer> 的实现。
下面我们将这个声明拆分一下,以便于理解。
Predicate<Integer> evenNumbers = i -> i %2 == 0;
long count = randIntStream.filter(evenNumbers).count();
到目前为止,filter() 方法是 java 流中与 Predicate 一起使用最多的方法。 Predicate 接口中还有其他默认方法。 and()、or()、negate() 和 isEqual() 方法使我们能够与其他Predicate实现逻辑 AND、OR 运算和逻辑 NOT。
我们现在将在下面看到其中的一些示例。
Predicate AND 应用
假设我们必须从流中过滤掉以字母“s”开头且长度大于 5 的名称。
final Stream<String> names = Stream.of(
"Brian", "Goetz", "Krishna", "Kishore",
"Scottee", "blabla", "Saraah", "Scott");
要过滤掉以“S”开头且超过 5 个字符的名称。我们可以采用两种方法。
方法1
简单的方法是将这两个条件填充在同一个Predicate中,如下所示。
Predicate<String> greaterThan5StartsWithS1 =
name -> name.startsWith("S") && name.length() > 5;
这种方法的优点是非常简单且易于编写。但缺点是不太灵活。例如,如果我们想根据另一个条件过滤掉,假设以“S”开头且长度为偶数的名称。然后,我们必须再次重写整个Predicate。
方法2
第二种方法是使用 and() 方法创建两个谓词并使用逻辑 AND,如下所示。
// 名称以 "S"开头的
Predicate<String> startsWithS = name -> name.startsWith("S");
// 名称长度大于 5
Predicate<String> lengthGreaterThan5 = name -> name.length() > 5;
// 两种一起
Predicate<String> greaterThan5StartsWithS2 =
startsWithS.and(lengthGreaterThan5);
// 使用两种联合
names.filter(greaterThan5StartsWithS2).count();
在第二种方法中,我们要编写相当多的代码。但我们可以避免一次又一次的Predicate重写。
这是一个非常小的例子。这种方法提供的灵活性更适合解决更大的问题。因为第二种方法中的Predicate是细粒度的。它们是可重复使用的,可以在我们项目中的任何地方、其他地方使用。
假设我们要过滤掉长度为偶数且以“S”开头的名称,我们可以简单地编写另一个处理长度为偶数的谓词,然后使用 and() 方法将它们组合起来,如下所示。
Predicate<String> evenLength = name -> name.length() %2 == 0;
Predicate<String> evenLenStartsWithS = evenLength.and(startsWithS);
现在我们需要长度大于 5 且长度为偶数的名称。我们已经有了这两个的Predicate。我们只需简单地与 and() 结合,如下所示。
Predicate<String> evenLengthGreaterThan5 =
evenLength.and(lengthGreaterThan5);
以上两种方法哪种更好?相信你已经知道答案了。
Predicate NOT 应用
现在我想过滤掉长度小于或等于5的名称。现在我们该怎么办?好吧,我们上面已经有一个名为 lengthGreaterThan5 的Predicate,
我们只需简单地调用 negate() 即可。执行逻辑非运算,给出我们想要的结果。
Predicate<String> lengthLessThanOrEq5 = lengthGreaterThan5.negate();
names.filter(lengthLessThanOrEq5).forEach(System.out::println);
优雅,相当优雅。我们可以使用这些Predicate随便操作我们想要的逻辑。
专业提示:不要将所有条件填充在一个Predicate中。每个 Predicate 对象使用一个条件,以便以后可以重复使用它们
Predicate OR 应用
现在,相信你心中已经知道怎么实现OR的逻辑了,我们只需调用 Predicate 对象的 or() 方法即可。
假设一个场景,我们想要长度大于 5 的名称或偶数长度的名称。这是执行逻辑或的Predicate。
Predicate<String> greaterThan5OrEven =
lengthGreaterThan5.or(evenLength);
完美。这就是Predicate的全部内容。如果你用心,我们可以实现更复杂的谓词,但这取决于你的需求。
总结
- Predicate表示具有一个参数的函数表达式并返回一个布尔值。
- 接口Predicate调用test(T t)的抽象方法,返回布尔值。
- 当我们想要从流中过滤掉一些结果时,Predicate会派上用场。或者甚至从集合中删除满足Predicate的元素。
- 接口Predicate具有三种default方法来执行逻辑AND、OR、 和逻辑非:分别为and()、or()和negate()。
猜你喜欢
- 2024-11-30 Java开发「函数式编程」——Lambda表达式
- 2024-11-30 玩转java8 lambda表达式三之对象方法引用
- 2024-11-30 Java 中的 Supplier:让数据生成更灵活
- 2024-11-30 Java 中的 Predicate:让判断逻辑更清晰灵活
- 2024-11-30 Java8精华-函数式编程(一)读完这篇,你将彻底理解
- 2024-11-30 你知道实现一个JAVA接口有几种方式吗?配合lambda更加意想不到
- 2024-11-30 巧妙应用 Java 的 Functional Interface:让代码更简洁、更灵活
- 2024-11-30 函数式接口编程真没那么难,简单几行让你的代码更优雅
- 2024-11-30 初窥函数式接口,不会取标题,没有噱头,全是干货
- 2024-11-30 JDK8新特性:函数式接口@FunctionalInterface
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)