专业编程基础技术教程

网站首页 > 基础教程 正文

java8精华-函数式编程-Predicate(四)

ccvgpt 2024-11-30 19:18:26 基础教程 1 ℃

在之前的文章

Java8精华-函数式编程(一)读完这篇,你将彻底理解

java8精华-函数式编程-Predicate(四)

Java8精华-函数式编程-Consumer(二)

java8精华-函数式编程-BiConsumer(三)

我们已经了解了ConsumerBiConsumer函数式接口。在本文中,我们将看到另一个重要的函数式接口,即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的全部内容。如果你用心,我们可以实现更复杂的谓词,但这取决于你的需求。

总结

  1. Predicate表示具有一个参数的函数表达式并返回一个布尔值。
  2. 接口Predicate调用test(T t)的抽象方法,返回布尔值。
  3. 当我们想要从流中过滤掉一些结果时,Predicate会派上用场。或者甚至从集合中删除满足Predicate的元素。
  4. 接口Predicate具有三种default方法来执行逻辑AND、OR、逻辑非:分别为and()or()negate()

最近发表
标签列表