跳到主要内容

Java Stream过滤

在Java 8引入的Stream API中,过滤操作是一个非常重要且常用的功能。通过过滤,我们可以从数据集合中筛选出满足特定条件的元素,这在数据处理过程中非常有用。本文将详细介绍Stream API中的过滤操作及其应用。

什么是Stream过滤?

Stream过滤是通过filter()方法实现的,该方法接收一个谓词(Predicate)作为参数,返回一个包含所有满足该谓词的元素的新Stream。简单来说,过滤就是根据某个条件筛选集合中的元素。

备注

谓词(Predicate)是一个函数式接口,表示一个接受单个输入参数并返回布尔值结果的函数。

基本用法

filter()方法的基本语法如下:

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

这个方法会返回一个新的Stream,其中包含原Stream中所有满足指定谓词的元素。

让我们来看一个简单的例子:

java
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()方法中使用逻辑运算符组合多个条件。

链式过滤

java
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]

组合条件过滤

java
List<Integer> result = numbers.stream()
.filter(num -> num % 2 == 0 && num > 5) // 组合条件
.collect(Collectors.toList());

System.out.println("大于5的偶数: " + result);

这两种方式的结果是相同的,但在某些情况下,一种方式可能比另一种更清晰或更高效。

使用自定义对象进行过滤

在实际应用中,我们经常需要过滤自定义对象的集合。来看一个示例:

java
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, 男)

复杂过滤逻辑

有时候,我们需要实现更复杂的过滤逻辑,可以将过滤条件封装成单独的方法:

java
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:数据过滤和转换

假设我们有一个订单系统,需要筛选出某个日期范围内的高价值订单:

java
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:数据清洗

在数据处理过程中,通常需要清洗掉不符合要求的数据:

java
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过滤的最佳实践

  1. 保持过滤条件简单明了:每个filter应该只关注一个职责,便于阅读和维护。

  2. 对复杂条件进行抽取:将复杂的过滤逻辑抽取为命名良好的Predicate实例或方法引用。

  3. 考虑性能因素

    • 将可能减少更多元素的过滤条件放在前面
    • 将计算成本高的过滤条件放在后面
  4. 避免副作用:过滤操作应该没有副作用,保持函数式编程的纯粹性。

  5. 结合其他Stream操作:filter通常与map、distinct、sorted等操作配合使用,形成完整的数据处理流水线。

总结

Stream的过滤操作是Java 8 Stream API中一个强大而灵活的功能。通过filter()方法,我们可以:

  • 基于单一或多个条件筛选集合元素
  • 使用链式调用或逻辑运算符组合多个过滤条件
  • 应用于各种数据类型,包括基本类型和自定义对象
  • 在实际应用场景中实现数据筛选、清洗等操作

掌握Stream过滤不仅能让你的代码更简洁、更具表达力,还能提高数据处理的效率和可读性。

练习

为了巩固所学知识,尝试完成以下练习:

  1. 创建一个包含多个字符串的列表,过滤出所有以特定字母开头且长度大于某个值的字符串。

  2. 创建一个学生对象列表,每个学生有姓名、年龄和多个科目成绩。过滤出年龄在某个范围内且所有科目成绩都及格的学生。

  3. 给定一个整数列表,过滤出符合以下条件的数字:是偶数且是3的倍数且大于10。

提示

记住,Stream过滤操作是一个中间操作,它总是需要一个终端操作(如collect、forEach、count等)来触发实际的计算。