Java Stream API 中的 filter() 方法详解

在 Java 8 中引入的 Stream API 提供了一种函数式编程风格的方式来处理集合类的数据。filter() 方法是 Stream 中最常用的操作之一,它用于根据给定的条件筛选出符合条件的元素。

1. filter() 方法概述

filter() 方法用于从流中筛选出符合特定条件的元素。它接收一个 Predicate 类型的参数,该参数是一个函数接口,用于对每个元素进行条件判断。

方法签名

Stream<T> filter(Predicate<? super T> predicate);

  • T:流中元素的类型。
  • predicate:一个返回布尔值的条件函数,对流中的每个元素进行判断。

返回值

返回一个新的流,其中只包含通过 predicate 条件测试的元素。

2. filter() 方法的工作原理

  • filter() 会遍历流中的每个元素,检查是否满足 Predicate 条件。
  • 只有通过 Predicate 条件测试的元素才会被保留,其他元素会被丢弃。
  • 这个操作是 惰性 的,意味着 filter() 本身并不会对数据进行立即计算,直到你触发终端操作(如 collect()forEach() 等)。

3. 示例:基本使用

假设我们有一个包含多个整数的列表,并想要筛选出其中的偶数:

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

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

        // 使用 filter 筛选出偶数
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());

        System.out.println("Even numbers: " + evenNumbers);
    }
}

输出:

Even numbers: [2, 4, 6, 8]

4. filter() 方法中的 Predicate

filter() 方法接收的 Predicate 是一个函数式接口,它表示一个接受单个输入参数并返回布尔值的函数。例如:n -> n % 2 == 0 就是一个 Predicate,它判断一个数字是否是偶数。

Predicate 接口的常用方法

  • test(T t):检查给定参数是否符合条件,返回布尔值。
  • and():与另一个 Predicate 合并,只有两个条件都为 true 时,结果才为 true
  • or():与另一个 Predicate 合并,至少有一个条件为 true 时,结果就为 true
  • negate():将条件取反。

5. 高级用法

5.1 多条件筛选

通过 Predicate 的 and() 或 or() 方法可以实现多条件筛选。

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

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

        // 使用 filter 和 and 筛选出大于 3 且为偶数的数字
        List<Integer> filteredNumbers = numbers.stream()
                                               .filter(n -> n > 3)
                                               .filter(n -> n % 2 == 0)
                                               .collect(Collectors.toList());

        System.out.println("Filtered numbers: " + filteredNumbers);
    }
}

输出:

Filtered numbers: [4, 6, 8]

5.2 结合其他操作

filter() 方法可以与其他 Stream 操作结合使用,形成更复杂的数据处理管道。

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

public class StreamFilterExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");

        // 筛选出以 'b' 开头的单词并转换为大写
        List<String> filteredWords = words.stream()
                                          .filter(word -> word.startsWith("b"))
                                          .map(String::toUpperCase)
                                          .collect(Collectors.toList());

        System.out.println("Filtered words: " + filteredWords);
    }
}

输出:

Filtered words: [BANANA]

5.3 空值过滤

filter() 方法还可以用于移除流中的空值。例如,过滤掉字符串流中的 null 元素:

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

public class StreamFilterExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", null, "cherry", "date", null);

        // 使用 filter 过滤掉 null 值
        List<String> nonNullWords = words.stream()
                                         .filter(word -> word != null)
                                         .collect(Collectors.toList());

        System.out.println("Non-null words: " + nonNullWords);
    }
}

输出:

Non-null words: [apple, banana, cherry, date]

5.4 惰性计算

filter() 是惰性计算的,这意味着在调用 filter() 时,它不会立即执行操作,只有在调用终端操作(如 collect()forEach() 等)时,才会触发计算。

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

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

        // 使用 filter 过滤出偶数,并通过 forEach 打印每个元素
        numbers.stream()
               .filter(n -> {
                   System.out.println("Filtering " + n);
                   return n % 2 == 0;
               })
               .forEach(System.out::println);
    }
}

输出:

Filtering 1
Filtering 2
2
Filtering 3
Filtering 4
4
Filtering 5
Filtering 6
6
Filtering 7
Filtering 8
8
Filtering 9

可以看到,filter() 会遍历所有的元素并判断条件,但只有在调用 forEach() 时,元素才会被真正打印。

6. 常见问题

  • 性能filter() 是惰性计算的,它只会在需要时才执行过滤操作,不会对所有元素都进行过滤,从而减少了不必要的计算。
  • 短路操作:与其他操作(如 findFirst()anyMatch() 等)配合使用时,filter() 可能会发挥短路特性,只对必要的元素进行处理。
  • 类型安全filter() 会保证类型安全,确保只能使用与流元素类型兼容的条件进行过滤。

7. 总结

  • filter() 方法是 Stream API 中的重要方法,用于基于条件筛选元素。
  • 它接收一个 Predicate 类型的函数参数,用于判断每个元素是否符合条件。
  • filter() 方法返回一个新的 Stream,原始 Stream 不会改变。
  • 它是惰性操作,只有在触发终端操作时才会执行过滤过程。
  • 可以与其他 Stream 操作(如 map()collect() 等)结合使用,形成功能强大的数据处理管道。

希望这些信息能帮助你更好地理解 Java Stream API 中的 filter() 方法。如果有其他问题或需要更详细的示例,随时告诉我!