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