1. 简介

在本篇文章中,我们将深入探讨 Java 中 Stream.filter() 方法的使用方式。这是处理 Java 8 引入的流(Stream)时非常常见的一个操作。

我们会重点讲解如何正确使用该方法,并特别关注在遇到受检异常(checked exception)时的一些处理技巧。

2. 使用 Stream.filter() 方法

filter()Stream 接口的一个中间操作(intermediate operation),它接收一个 Predicate 函数式接口作为参数,用于过滤出满足条件的元素:

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

为了更好地演示,我们先定义一个简单的 Customer 类:

public class Customer {
    private String name;
    private int points;
    // 构造函数和标准 getter 省略
}

然后创建一组客户数据:

Customer john = new Customer("John P.", 15);
Customer sarah = new Customer("Sarah M.", 200);
Customer charles = new Customer("Charles B.", 150);
Customer mary = new Customer("Mary T.", 1);

List<Customer> customers = Arrays.asList(john, sarah, charles, mary);

2.1. 对集合进行过滤

最常见的用法就是对集合进行筛选,比如找出积分超过 100 的客户。我们可以使用 Lambda 表达式来实现:

List<Customer> customersWithMoreThan100Points = customers
  .stream()
  .filter(c -> c.getPoints() > 100)
  .collect(Collectors.toList());

当然,如果你已经有一个现成的方法,也可以使用方法引用(method reference)来简化写法:

List<Customer> customersWithMoreThan100Points = customers
  .stream()
  .filter(Customer::hasOverHundredPoints)
  .collect(Collectors.toList());

其中 hasOverHundredPoints() 方法定义如下:

public boolean hasOverHundredPoints() {
    return this.points > 100;
}

两种写法的结果是一样的:

assertThat(customersWithMoreThan100Points).hasSize(2);
assertThat(customersWithMoreThan100Points).contains(sarah, charles);

2.2. 多条件过滤

有时候我们需要多个条件同时满足,这时候可以在 filter() 中使用逻辑运算符组合多个判断:

List<Customer> charlesWithMoreThan100Points = customers
  .stream()
  .filter(c -> c.getPoints() > 100 && c.getName().startsWith("Charles"))
  .collect(Collectors.toList());

assertThat(charlesWithMoreThan100Points).hasSize(1);
assertThat(charlesWithMoreThan100Points).contains(charles);

✅ 这种写法非常直观,也体现了 Stream API 的强大表达能力。

3. 异常处理问题

到目前为止,我们的过滤逻辑都没有抛出异常。但实际上,Java 的函数式接口(如 Predicate)并不允许直接抛出受检异常。

如果我们尝试在 filter() 中使用会抛出受检异常的方法,就会编译报错。下面来看几种解决方案。

3.1. 自定义包装方式

假设我们在 Customer 类中新增一个字段表示头像地址:

private String profilePhotoUrl;

并添加一个方法来校验头像是否有效:

public boolean hasValidProfilePhoto() throws IOException {
    URL url = new URL(this.profilePhotoUrl);
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    return connection.getResponseCode() == HttpURLConnection.HTTP_OK;
}

此时如果我们直接这样写:

List<Customer> customersWithValidProfilePhoto = customers
  .stream()
  .filter(Customer::hasValidProfilePhoto)
  .collect(Collectors.toList());

就会报错:

Incompatible thrown types java.io.IOException in functional expression

⚠️ 因为 Predicate 接口不支持受检异常。

解决办法之一是手动包装成 try-catch 块:

List<Customer> customersWithValidProfilePhoto = customers
  .stream()
  .filter(c -> {
      try {
          return c.hasValidProfilePhoto();
      } catch (IOException e) {
          // 可以选择忽略、记录日志或返回 false
      }
      return false;
  })
  .collect(Collectors.toList());

❌ 如果你确实需要抛出异常,可以把它封装为非受检异常(unchecked exception)再抛出。

3.2. 使用 ThrowingFunction 库

另一种优雅的方式是借助第三方库 ThrowingFunction,它专门用来处理 Java 函数式接口中的受检异常。

首先添加依赖:

<dependency>    
    <groupId>com.pivovarit</groupId>    
    <artifactId>throwing-function</artifactId>    
    <version>1.5.1</version>    
</dependency>

然后就可以简单粗暴地使用它的 ThrowingPredicate.unchecked() 方法:

List<Customer> customersWithValidProfilePhoto = customers
  .stream()
  .filter(ThrowingPredicate.unchecked(Customer::hasValidProfilePhoto))
  .collect(Collectors.toList());

✅ 这样写既简洁又安全,避免了大量样板代码。

4. 总结

本文通过几个典型示例介绍了 Stream.filter() 在实际开发中的使用方式,包括基本过滤、多条件过滤以及异常处理的应对策略。

虽然 Stream API 很强大,但在涉及异常时还是要小心处理,特别是受检异常的问题,不能掉以轻心。

完整代码可以在 GitHub 上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-streams-simple