使用 Java Stream 过滤器应用多个函数式接口

本文介绍了如何使用 Java Stream API 中的 filter 方法,结合多个函数式接口(Predicate)来实现复杂的过滤逻辑。重点讲解了如何将多个 Predicate 组合成一个,以满足不同的过滤需求,例如:所有条件都满足、至少一个条件满足等。同时,也指出了自定义函数式接口的必要性,并推荐使用 Java 内置的 Predicate 接口。

使用 Predicate 接口进行过滤

Java 8 引入了函数式接口 Predicate,它表示一个布尔值函数,接受一个参数并返回一个布尔值。Predicate 非常适合用于 Stream 的 filter 操作,可以方便地定义各种过滤条件。

例如,假设我们有一个 Student 类,并且想要过滤出所有年龄大于 18 岁的学生:

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Getter;

class Student {
    @Getter
    private LocalDate birthDate;

    public Student(LocalDate birthDate) {
        this.birthDate = birthDate;
    }
}

public class PredicateExample {

    public static void main(String[] args) {
        List students = List.of(
            new Student(LocalDate.of(2000, 1, 1)),
            new Student(LocalDate.of(2005, 1, 1)),
            new Student(LocalDate.of(1995, 1, 1))
        );

        Predicate isAdult = s -> ChronoUnit.YEARS.between(LocalDate.now(), s.getBirthDate()) >= 18;

        List adultStudents = students.stream()
            .filter(isAdult)
            .collect(Collectors.toList());

        System.out.println("Adult Students Count: " + adultStudents.size()); // Output: 3
    }
}

组合多个 Predicate

当需要应用多个过滤条件时,可以将多个 Predicate 组合成一个。Predicate 接口提供了 and、or 和 negate 方法来实现逻辑与、逻辑或和逻辑非操作。

1. 所有条件都满足(逻辑与)

如果需要所有条件都满足,可以使用 Predicate.and() 方法。

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Getter;

class Student {
    @Getter
    private LocalDate birthDate;

    @Getter
    private String name;

    public Student(LocalDate birthDate, String name) {
        this.birthDate = birthDate;
        this.name = name;
    }
}

public class CombinedPredicateExample {

    public static void main(String[] args) {
        List students = List.of(
            new Student(LocalDate.of(2000, 1, 1), "Alice"),
            new Student(LocalDate.of(2005, 1, 1), "Bob"),
            new Student(LocalDate.of(1995, 1, 1), "Charlie"),
            new Student(LocalDate.of(2000, 1, 1), "David")
        );

        Predicate isAdult = s -> ChronoUnit.YEARS.between(LocalDate.now(), s.getBirthDate()) >= 18;
        Predicate nameStartsWithA = s -> s.getName().startsWith("A");

        Predicate combinedPredicate = isAdult.and(nameStartsWithA);

        List filteredStudents = students.stream()
            .filter(combinedPredicate)
            .collect(Collectors.toList());

        System.out.println("Filtered Students Count: " + filteredStudents.size()); // Output: 1
    }
}

2. 至少一个条件满足(逻辑或)

如果只需要至少一个条件满足,可以使用 Predicate.or() 方法。

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Getter;

class Student {
    @Getter
    private LocalDate birthDate;

    @Getter
    private String name;

    public Student(LocalDate birthDate, String name) {
        this.birthDate = birthDate;
        this.name = name;
    }
}

public class CombinedPredicateExample {

    public static void main(String[] args) {
        List students = List.of(
            new Student(LocalDate.of(2000, 1, 1), "Alice"),
            new Student(LocalDate.of(2005, 1, 1), "Bob"),
            new Student(LocalDate.of(1995, 1, 1), "Charlie"),
            new Student(LocalDate.of(2000, 1, 1), "David")
        );

        Predicate isAdult = s -> ChronoUnit.YEARS.between(LocalDate.now(), s.getBirthDate()) >= 18;
        Predicate nameStartsWithA = s -> s.getName().startsWith("A");

        Predicate combinedPredicate = isAdult.or(nameStartsWithA);

        List filteredStudents = students.stream()
            .filter(combinedPredicate)
            .collect(Collectors.toList());

        System.out.println("Filtered Students Count: " + filteredStudents.size()); // Output: 4
    }
}

3. 使用循环组合多个 Predicate

如果 Predicate 的数量很多,可以使用循环来组合它们。

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Getter;

class Student {
    @Getter
    private LocalDate birthDate;

    @Getter
    private String name;

    public Student(LocalDate birthDate, String name) {
        this.birthDate = birthDate;
        this.name = name;
    }
}

public class CombinedPredicateExample {

    public static void main(String[] args) {
        List students = List.of(
            new Student(LocalDate.of(2000, 1, 1), "Alice"),
            new Student(LocalDate.of(2005, 1, 1), "Bob"),
            new Student(LocalDate.of(1995, 1, 1), "Charlie"),
            new Student(LocalDate.of(2000, 1, 1), "David")
        );

        List
> predicates = new ArrayList<>();
        predicates.add(s -> ChronoUnit.YEARS.between(LocalDate.now(), s.getBirthDate()) >= 18);
        predicates.add(s -> s.getName().startsWith("A"));
        predicates.add(s -> s.getName().length() > 3);

        Predicate combinedPredicate = predicates.stream()
            .reduce(Predicate.isEqual(true), Predicate::and);

        List filteredStudents = students.stream()
            .filter(combinedPredicate)
            .collect(Collectors.toList());

        System.out.println("Filtered Students Count: " + filteredStudents.size()); // Output: 1
    }
}

避免自定义重复的函数式接口

通常情况下,没有必要自定义类似 FilterClass 的函数式接口。Java 已经提供了 Predicate 接口,它已经满足了大多数过滤需求。自定义接口会增加代码的复杂性,并且没有带来额外的优势。

总结

通过本文,我们学习了如何使用 Java Stream API 中的 filter 方法,结合多个 Predicate 来实现复杂的过滤逻辑。我们了解了如何使用 and、or 和循环来组合多个 Predicate,并避免自定义重复的函数式接口。掌握这些技巧可以帮助我们编写更加简洁、高效的 Java 代码。