Java Stream过滤
在Java 8引入的Stream API中,过滤操作是一个非常重要且常用的功能。通过过滤,我们可以从数据集合中筛选出满足特定条件的元素,这在数据处理过程中非常有用。本文将详细介绍Stream API中的过滤操作及其应用。
什么是Stream过滤?
Stream过滤是通过filter()
方法实现的,该方法接收一个谓词(Predicate)作为参数,返回一个包含所有满足该谓词的元素的新Stream。简单来说,过滤就是根据某个条件筛选集合中的元素。
谓词(Predicate)是一个函数式接口,表示一个接受单个输入参数并返回布尔值结果的函数。
基本用法
filter()
方法的基本语法如下:
Stream<T> filter(Predicate<? super T> predicate)
这个方法会返回一个新的Stream,其中包含原Stream中所有满足指定谓词的元素。
让我们来看一个简单的例子:
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, 10);
// 过滤出偶数
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());
System.out.println("原始数字: " + numbers);
System.out.println("偶数: " + evenNumbers);
}
}
输出:
原始数字: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
偶数: [2, 4, 6, 8, 10]
在这个例子中,我们使用filter()
方法从一个包含1到10的整数列表中筛选出所有偶数。
多条件过滤
你可以使用多个filter()
方法链式调用,或者在一个filter()
方法中使用逻辑运算符组合多个条件。
链式过滤
List<Integer> result = numbers.stream()
.filter(num -> num % 2 == 0) // 过滤偶数
.filter(num -> num > 5) // 过滤大于5的数
.collect(Collectors.toList());
System.out.println("大于5的偶数: " + result);
输出:
大于5的偶数: [6, 8, 10]
组合条件过滤
List<Integer> result = numbers.stream()
.filter(num -> num % 2 == 0 && num > 5) // 组合条件
.collect(Collectors.toList());
System.out.println("大于5的偶数: " + result);
这两种方式的结果是相同的,但在某些情况下,一种方式可能比另一种更清晰或更高效。
使用自定义对象进行过滤
在实际应用中,我们经常需要过滤自定义对象的集合。来看一个示例:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Person {
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getGender() { return gender; }
@Override
public String toString() {
return name + " (" + age + ", " + gender + ")";
}
}
public class PersonFilterExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("张三", 18, "男"));
people.add(new Person("李四", 22, "男"));
people.add(new Person("王五", 30, "男"));
people.add(new Person("赵六", 25, "女"));
people.add(new Person("钱七", 17, "女"));
// 过滤年龄大于20的男性
List<Person> filteredPeople = people.stream()
.filter(p -> p.getAge() > 20)
.filter(p -> p.getGender().equals("男"))
.collect(Collectors.toList());
System.out.println("年龄大于20的男性:");
filteredPeople.forEach(System.out::println);
}
}
输出:
年龄大于20的男性:
李四 (22, 男)
王五 (30, 男)
复杂过滤逻辑
有时候,我们需要实现更复杂的过滤逻辑,可以将过滤条件封装成单独的方法:
import java.util.function.Predicate;
public class ComplexFilterExample {
public static void main(String[] args) {
List<Person> people = getPeopleList(); // 假设这个方法返回一个Person列表
// 创建过滤谓词
Predicate<Person> isAdult = p -> p.getAge() >= 18;
Predicate<Person> isMale = p -> p.getGender().equals("男");
Predicate<Person> hasShortName = p -> p.getName().length() <= 2;
// 组合谓词
List<Person> result = people.stream()
.filter(isAdult.and(isMale).and(hasShortName))
.collect(Collectors.toList());
System.out.println("成年男性且名字长度不超过2个字符:");
result.forEach(System.out::println);
}
}
在这个例子中,我们使用了Predicate
接口提供的and()
、or()
和negate()
方法来组合多个条件。
实际应用场景
场景1:数据过滤和转换
假设我们有一个订单系统,需要筛选出某个日期范围内的高价值订单:
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
class Order {
private String id;
private double amount;
private LocalDate orderDate;
// 构造函数、getter和setter省略
@Override
public String toString() {
return "Order{" + "id='" + id + "', amount=" + amount +
", orderDate=" + orderDate + '}';
}
}
public class OrderFilterExample {
public static void main(String[] args) {
List<Order> orders = getOrders(); // 假设这个方法返回所有订单
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 3, 31);
// 过滤第一季度金额大于1000的订单
List<Order> highValueOrders = orders.stream()
.filter(order -> order.getOrderDate().isAfter(startDate) ||
order.getOrderDate().isEqual(startDate))
.filter(order -> order.getOrderDate().isBefore(endDate) ||
order.getOrderDate().isEqual(endDate))
.filter(order -> order.getAmount() > 1000)
.collect(Collectors.toList());
System.out.println("第一季度的高价值订单:");
highValueOrders.forEach(System.out::println);
}
}
场景2:数据清洗
在数据处理过程中,通常需要清洗掉不符合要求的数据:
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
class DataPoint {
private String id;
private Double value;
// 构造函数、getter和setter省略
}
public class DataCleaningExample {
public static void main(String[] args) {
List<DataPoint> dataPoints = getRawData(); // 获取原始数据
// 清洗数据:去除null值、异常值等
List<DataPoint> cleanedData = dataPoints.stream()
.filter(Objects::nonNull) // 过滤掉null对象
.filter(dp -> dp.getId() != null && !dp.getId().isEmpty()) // 过滤掉ID为空的数据
.filter(dp -> dp.getValue() != null) // 过滤掉值为null的数据
.filter(dp -> dp.getValue() >= 0 && dp.getValue() <= 100) // 过滤掉范围外的值
.collect(Collectors.toList());
System.out.println("清洗后的数据点数量: " + cleanedData.size());
}
}
Stream过滤的最佳实践
-
保持过滤条件简单明了:每个filter应该只关注一个职责,便于阅读和维护。
-
对复杂条件进行抽取:将复杂的过滤逻辑抽取为命名良好的Predicate实例或方法引用。
-
考虑性能因素:
- 将可能减少更多元素的过滤条件放在前面
- 将计算成本高的过滤条件放在后面
-
避免副作用:过滤操作应该没有副作用,保持函数式编程的纯粹性。
-
结合其他Stream操作:filter通常与map、distinct、sorted等操作配合使用,形成完整的数据处理流水线。
总结
Stream的过滤操作是Java 8 Stream API中一个强大而灵活的功能。通过filter()
方法,我们可以:
- 基于单一或多个条件筛选集合元素
- 使用链式调用或逻辑运算符组合多个过滤条件
- 应用于各种数据类型,包括基本类型和自定义对象
- 在实际应用场景中实现数据筛选、清洗等操作
掌握Stream过滤不仅能让你的代码更简洁、更具表达力,还能提高数据处理的效率和可读性。
练习
为了巩固所学知识,尝试完成以下练习:
-
创建一个包含多个字符串的列表,过滤出所有以特定字母开头且长度大于某个值的字符串。
-
创建一个学生对象列表,每个学生有姓名、年龄和多个科目成绩。过滤出年龄在某个范围内且所有科目成绩都及格的学生。
-
给定一个整数列表,过滤出符合以下条件的数字:是偶数且是3的倍数且大于10。
记住,Stream过滤操作是一个中间操作,它总是需要一个终端操作(如collect、forEach、count等)来触发实际的计算。