MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Stream filter 方法的复杂条件过滤

2022-11-134.4k 阅读

Java Stream filter 方法的复杂条件过滤

在 Java 编程中,Stream API 是一个强大的工具,它允许我们以一种声明式的方式处理集合数据。其中的 filter 方法用于根据特定条件过滤流中的元素。在实际应用中,我们常常会遇到需要使用复杂条件进行过滤的场景。本文将深入探讨如何在 Java Stream 中使用 filter 方法进行复杂条件过滤,并通过丰富的代码示例帮助您更好地理解和掌握这一技巧。

1. 简单条件过滤回顾

在开始复杂条件过滤之前,让我们先回顾一下 filter 方法的基本用法。filter 方法接受一个 Predicate 参数,该 Predicate 定义了一个布尔条件,流中的每个元素都会根据这个条件进行评估,只有满足条件的元素才会被保留在流中。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SimpleFilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> evenNumbers = numbers.stream()
               .filter(n -> n % 2 == 0)
               .collect(Collectors.toList());

        System.out.println(evenNumbers);
    }
}

在上述代码中,我们使用 filter 方法过滤出了列表中的偶数。n -> n % 2 == 0 就是一个简单的 Predicate,它定义了判断偶数的条件。

2. 组合多个简单条件

实际场景中,我们可能需要组合多个简单条件来形成更复杂的过滤逻辑。Java 中的 Predicate 接口提供了一些方法来帮助我们组合条件,如 andornegate

2.1 使用 and 方法组合条件

and 方法用于组合两个 Predicate,只有当两个 Predicate 都为 true 时,组合后的 Predicate 才为 true

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class AndConditionExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> filteredNumbers = numbers.stream()
               .filter(n -> n % 2 == 0)
               .filter(n -> n > 5)
               .collect(Collectors.toList());

        // 使用 and 方法组合条件
        List<Integer> filteredNumbersWithAnd = numbers.stream()
               .filter(n -> n % 2 == 0 && n > 5)
               .collect(Collectors.toList());

        System.out.println(filteredNumbers);
        System.out.println(filteredNumbersWithAnd);
    }
}

在上述代码中,我们首先使用两个 filter 方法依次过滤出偶数并且大于 5 的数。然后,我们使用 and 方法将两个条件组合在一个 filter 方法中,得到相同的结果。

2.2 使用 or 方法组合条件

or 方法用于组合两个 Predicate,只要其中一个 Predicatetrue,组合后的 Predicate 就为 true

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class OrConditionExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> filteredNumbers = numbers.stream()
               .filter(n -> n % 2 == 0 || n < 3)
               .collect(Collectors.toList());

        System.out.println(filteredNumbers);
    }
}

在这个例子中,我们过滤出了偶数或者小于 3 的数。

2.3 使用 negate 方法取反条件

negate 方法用于对 Predicate 进行取反操作。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class NegateConditionExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> filteredNumbers = numbers.stream()
               .filter(n ->!(n % 2 == 0))
               .collect(Collectors.toList());

        // 使用 negate 方法
        List<Integer> filteredNumbersWithNegate = numbers.stream()
               .filter(Predicate.isEqual(2).negate())
               .collect(Collectors.toList());

        System.out.println(filteredNumbers);
        System.out.println(filteredNumbersWithNegate);
    }
}

在上述代码中,第一个 filter 使用了常规的取反操作过滤出奇数。第二个 filter 使用 negate 方法对判断是否等于 2 的 Predicate 进行取反,过滤出不等于 2 的数。

3. 基于对象属性的复杂条件过滤

当处理对象集合时,我们通常需要根据对象的属性进行复杂条件过滤。

3.1 自定义对象类

首先,我们创建一个简单的自定义对象类 Person

public class Person {
    private String name;
    private int age;
    private boolean isMale;

    public Person(String name, int age, boolean isMale) {
        this.name = name;
        this.age = age;
        this.isMale = isMale;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public boolean isMale() {
        return isMale;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMale=" + isMale +
                '}';
    }
}

3.2 根据多个属性过滤

现在,我们使用 filter 方法根据 Person 对象的多个属性进行复杂条件过滤。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ObjectFilterExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        List<Person> filteredPeople = people.stream()
               .filter(p -> p.getAge() > 25 && p.isMale())
               .collect(Collectors.toList());

        System.out.println(filteredPeople);
    }
}

在上述代码中,我们过滤出了年龄大于 25 且为男性的 Person 对象。

4. 复杂条件中的逻辑嵌套

在某些情况下,我们的复杂条件可能包含逻辑嵌套,即条件内部还有子条件。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class NestedConditionExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> filteredNumbers = numbers.stream()
               .filter(n -> (n % 2 == 0 && n > 5) || (n % 3 == 0 && n < 8))
               .collect(Collectors.toList());

        System.out.println(filteredNumbers);
    }
}

在这个例子中,我们的过滤条件是一个逻辑嵌套的表达式,要么是偶数且大于 5,要么是能被 3 整除且小于 8 的数。

5. 动态生成复杂过滤条件

有时候,我们需要根据运行时的情况动态生成复杂的过滤条件。

5.1 根据用户输入生成条件

假设我们有一个程序,根据用户输入的年龄范围和性别来过滤 Person 对象。

import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

public class DynamicConditionExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter min age: ");
        int minAge = scanner.nextInt();
        System.out.println("Enter max age: ");
        int maxAge = scanner.nextInt();
        System.out.println("Enter gender (true for male, false for female): ");
        boolean isMale = scanner.nextBoolean();

        List<Person> filteredPeople = people.stream()
               .filter(p -> p.getAge() >= minAge && p.getAge() <= maxAge && p.isMale() == isMale)
               .collect(Collectors.toList());

        System.out.println(filteredPeople);
    }
}

在上述代码中,我们根据用户输入的最小年龄、最大年龄和性别来动态生成过滤条件。

5.2 根据配置文件生成条件

另一种常见的场景是根据配置文件生成过滤条件。假设我们有一个配置文件 config.properties,内容如下:

minAge = 25
maxAge = 35
isMale = true

我们可以读取这个配置文件并根据配置生成过滤条件。

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

public class ConfigBasedConditionExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        Properties properties = new Properties();
        try (FileInputStream fis = new FileInputStream("config.properties")) {
            properties.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        }

        int minAge = Integer.parseInt(properties.getProperty("minAge"));
        int maxAge = Integer.parseInt(properties.getProperty("maxAge"));
        boolean isMale = Boolean.parseBoolean(properties.getProperty("isMale"));

        List<Person> filteredPeople = people.stream()
               .filter(p -> p.getAge() >= minAge && p.getAge() <= maxAge && p.isMale() == isMale)
               .collect(Collectors.toList());

        System.out.println(filteredPeople);
    }
}

在这个例子中,我们从配置文件中读取年龄范围和性别信息,并根据这些信息生成过滤条件。

6. 性能考虑

在使用复杂条件过滤时,性能是一个需要考虑的重要因素。复杂的条件可能会增加每个元素的评估时间,从而影响整个流处理的性能。

6.1 减少不必要的计算

尽量在条件中避免不必要的计算。例如,如果某个条件在大多数情况下为 false,可以将其放在前面,这样可以尽早过滤掉不符合条件的元素,减少后续复杂条件的计算。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class PerformanceExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        // 先过滤掉不符合简单条件的元素
        List<Person> filteredPeople = people.stream()
               .filter(p -> p.isMale())
               .filter(p -> p.getAge() > 25 && p.getAge() < 35)
               .collect(Collectors.toList());

        System.out.println(filteredPeople);
    }
}

在上述代码中,我们先根据性别进行过滤,因为性别判断相对简单,这样可以减少后续年龄范围判断的次数。

6.2 使用并行流时的注意事项

当使用并行流进行复杂条件过滤时,需要注意线程安全问题。确保条件中的操作是线程安全的,否则可能会导致不可预测的结果。

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class ParallelStreamPerformanceExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        AtomicInteger counter = new AtomicInteger();
        List<Person> filteredPeople = people.parallelStream()
               .filter(p -> {
                    // 这里的操作需要是线程安全的
                    counter.incrementAndGet();
                    return p.getAge() > 25 && p.isMale();
                })
               .collect(Collectors.toList());

        System.out.println(filteredPeople);
        System.out.println("Counter: " + counter.get());
    }
}

在上述代码中,我们使用 AtomicInteger 来确保在并行流中计数操作的线程安全性。

7. 结合其他 Stream 操作

复杂条件过滤通常不会单独使用,而是会与其他 Stream 操作结合使用,以实现更强大的数据处理功能。

7.1 与 map 操作结合

map 操作可以在过滤后对元素进行转换。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapAfterFilterExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25, false),
                new Person("Bob", 30, true),
                new Person("Charlie", 20, true),
                new Person("David", 35, true),
                new Person("Eve", 28, false)
        );

        List<String> names = people.stream()
               .filter(p -> p.getAge() > 25 && p.isMale())
               .map(Person::getName)
               .collect(Collectors.toList());

        System.out.println(names);
    }
}

在这个例子中,我们先过滤出年龄大于 25 且为男性的 Person 对象,然后使用 map 操作将这些对象转换为他们的名字。

7.2 与 reduce 操作结合

reduce 操作可以对过滤后的元素进行聚合。

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ReduceAfterFilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        Optional<Integer> sum = numbers.stream()
               .filter(n -> n % 2 == 0)
               .reduce((a, b) -> a + b);

        sum.ifPresent(System.out::println);
    }
}

在上述代码中,我们先过滤出偶数,然后使用 reduce 操作计算这些偶数的总和。

通过深入理解和掌握 Java Stream filter 方法的复杂条件过滤技巧,我们可以更加高效、灵活地处理集合数据,满足各种复杂的业务需求。无论是简单的条件组合,还是基于对象属性、动态生成条件以及与其他 Stream 操作的结合,都为我们在 Java 编程中提供了强大的数据处理能力。希望本文的内容和示例代码能帮助您在实际项目中更好地应用这一特性。