如何在Java中使用Lambda表达式进行流过滤

456 阅读5分钟

使用Lambda表达式在Java中进行流过滤

Java提供了一些复杂的方法,对流和lambda表达式进行最佳利用。这些方法允许你使用函数式编程原则建立一个装配线。

其中一个方法,filter(),是一个中间操作,它从流中接收数据,并在根据条件改变数据后产生一个新流。

简介

在本指南中,我们将看看如何充分地使用这个方法。

前提条件

在学习本教程之前,读者应该。

  • 具备Java编程语言的基本知识。
  • 有一些使用Java流的工作经验。
  • 了解maven的基础知识。

Java中的流过滤方法概述

Java流的filter()函数允许你根据一个标准缩小流中的项目。如果你只想要那些在你的列表中的项目,你可以使用filter方法来做到这一点。这个方法接受一个谓词作为输入,并返回一个作为该谓词结果的元素的列表。

可以用filter()方法从这个流中获得符合特定谓词的项目流。这是一个介于两者之间的过程。执行像filter()这样的中间操作并没有真正过滤任何东西,而是生成了一个新的流。这个流在浏览时,包括第一个流中满足所提供的谓词的项目。这些操作始终是懒惰的。

使用流的过滤方法

Java stream提供了filter()方法,它允许你根据你指定的谓词来过滤流元素。通过使用过滤器方法,你可以方便地从你的列表中只获得偶数元素。这个方法接受一个谓词作为参数,并返回一个作为谓词结果的元素流。

一个流接口的filter()方法可以识别流中满足某个标准的元素。它是一个流接口的中间操作。

下面是Stream filter()函数的方法签名。

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

注意它如何接受一个谓词对象作为参数。一个谓词是一个功能接口的逻辑接口。因此,你也可以向这个函数发送一个lambda表达式。

集合过滤

filter()函数经常被用来处理集合。我们可以用它来创建一个获得90分以上的工人列表,将谓词指定为lambda。

// Assume Employee to be a POJO (plain old java object) with the employee's identity and marks
Employee george = new Employee("George", 91);
Employee mike = new Employee("Mike", 95);

List<Employee> employees = List.of(
        george,
        mike,
        new Employee("Debra", 80),
        new Employee("Robbert", 50)
);

List<Employee> employeeWith90MarksAndAbove = employees
  .stream()
  .filter(q -> q.getMarks() > 90)
  .collect(Collectors.toList());

此外,也可以使用方法引用,这也是这种lambda表达式的简写。

Employee george = new Employee("George", 91);
Employee mike = new Employee("Mike", 95);

List<Employee> employees = List.of(
        george,
        mike,
        new Employee("Debra", 80),
        new Employee("Robbert", 50)
);

List<Employee> employeeWith90MarksAndAbove = employees
  .stream()
  .filter(Employee::hasOverNinetyMarks)
  .collect(Collectors.toList());

在这个特殊的例子中,我们通过添加hasOverNinetyMarks方法来增强我们的Employee类。

public boolean hasOverNinetyMarks() 
{
    return this.marks > 90;
}

当我们使用这两种方法时,我们会得到相同的结果。

assert employeeWith90MarksAndAbove.contains(george) && employeeWith90MarksAndAbove.contains(mike);
assert employeeWith90MarksAndAbove.size() == 2;

assert关键字保证了程序中任何假设的准确性;当我们执行一个断言时,它被假定为真实。如果断言是不真实的,JVM将引发一个断言错误。请确保为你的虚拟机设置-ea 选项(启用断言),以便能够使用断言。

根据各种标准过滤数据

此外,我们可以利用过滤器的几个标准来实现我们的优势。

例如,我们可能会使用点数和名称的组合来缩小结果。

Employee george = new Employee("George", 91);
Employee mike = new Employee("Mike", 95);

List<Employee> employees = List.of(
        george,
        mike,
        new Employee("Debra", 80),
        new Employee("Robbert", 50)
);

List<Employee> georgeWith90MarksAndAbove = employees
  .stream()
  .filter(q -> q.getMarks() > 90 && q.getIdentity().startsWith("George"))
  .collect(Collectors.toList());

assert georgeWith90MarksAndAbove.size() == 1;
assert georgeWith90MarksAndAbove.contains(george)

解释

我们用filter()的多个条件,如分数和雇员的身份。

处理异常情况的方法

filter方法是用来评估那些在评估时不抛出异常的谓词。Java编程语言的功能接口没有规定任何种类的异常,无论是检查还是不检查。JDK给出的功能接口不足以处理异常;在处理异常时,所产生的代码会变得很复杂,很麻烦。

接下来将使用几种替代方法来详细探讨λ表达式中的异常处理问题。

使用一个自定义的包装器

为我们的Employee对象添加一个profilePictureUrl ,将是我们做的第一件事。

private String profilePictureUrl;

此外,我们将创建一个简单的hasValidProfilePicture() 函数来验证个人资料图片是否仍然有效。

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

当调用hasValidProfilePicture() 这个方法时,会发出一个IOException。现在,如果我们试图根据这个标准对客户进行排序。我们将得到以下结果。

List<Employee> employeesWithValidProfilePicture = employees
  .stream()
  .filter(Employee::hasValidProfilePicture)
  .collect(Collectors.toList());

解释

用hasValidProfile这个方法来过滤员工,会给我们带来一个编译错误。

java: incompatible thrown types java.io.IOException in functional expression

如下图所示,处理的方法之一是将其封装在try-catch块中。

List<Employee> employeesWithValidProfilePicture = employees
        .stream()
        .filter(q ->
        {
            try {
                return q.hasValidProfilePicture();
            }    catch (IOException x) {
                 // needs to take care of the stated exception
                 return false;
            }
        }).collect(Collectors.toList());

为了防止我们的谓词产生的异常被捕获,你可以把它封装在一个未检查的异常中,如来自RuntimeException的异常。

使用 ThrowingFunction

我们也可以使用ThrowingFunction 库作为替代。

使用ThrowingFunction(这是一个免费的开源包,可以下载),我们可以相对简单地处理Java函数式接口中的检查性异常。

第一步是在我们的pom.xml文件中包含抛出函数的依赖关系。

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

通过ThrowingPredicate 类,谓词中的异常处理被简化了,该类还包含了用于封装检查过的异常的unchecked()方法。

这个动作在下面的代码中有所说明。

List<Employee> employeesWithValidProfilePicture = employees
        .stream()
        .filter(ThrowingPredicate.unchecked(Employee::hasValidProfilePicture))
        .collect(Collectors.toList());

结语

在本教程中,我们研究了如何使用Java中的filter()方法来过滤掉流中的特定项目。在这个过程中,我们使用了lambda表达式来指定要过滤的谓词。除此之外,我们还研究了处理异常情况的不同方法。