带步长的循环转换
此页面由 Venkat Subramaniam 在 UPL 许可下贡献带步长的迭代
在本系列的上一篇 教程 中,我们研究了如何将命令式风格编写的简单循环转换为函数式风格。在本文中,我们将了解如何处理更复杂的循环,即当我们需要在某个区间内跳过一些值时。
在逐个循环遍历一系列值时,IntStream 的 range() 方法非常适合在函数式风格中实现。此方法返回一个流,该流将为指定范围内的值逐个生成一个值。乍一看,为了跳过一些值,我们可能会倾向于在流上使用 filter() 方法。但是,有一个更简单的解决方案,即 IntStream 的 iterate() 方法。
从命令式到函数式风格
以下是一个使用步长跳过所需范围内的某些值的循环
for(int i = 0; i < 15; i = i + 3) {
System.out.println(i);
}
索引变量 i 的值从 0 开始,然后在迭代向前移动时递增 3。当您发现自己正在查看一个类似的循环,其中迭代不是遍历范围内的每个值,而是跳过一些值时,请考虑使用 IntStream 的 iterate() 方法。
在重构代码之前,让我们仔细看看上一段代码中的 for() 循环,但戴上一副想象中的眼镜,让我们能够看到潜在的 lambda 用途。
//imaginary code
for(int i = 0; i < 15; i = i + 3) //imperative
for(seed, i -> i < 15, i -> i + 3) //functional
传递给 for 循环的第一个参数是迭代的起始值或种子,它可以保持原样。第二个参数是一个谓词,它告诉索引变量 i 的值不应超过 15 的值。我们可以在函数式风格中用 IntPredicate 替换它。第三个参数是递增索引变量的值,在函数式风格中,它只是一个 IntUnaryOperator。IntStream 接口有一个名为 iterate() 的静态方法,它很好地表示了想象中的代码:iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)。
让我们重构循环以使用函数式风格。
import java.util.stream.IntStream;
...
IntStream.iterate(0, i -> i < 15, i -> i + 3)
.forEach(System.out::println);
这非常简单,; 变成了 ,,我们使用了两个 lambda:一个用于 IntPredicate,另一个用于 IntUnaryOperator。
除了跳过值之外,我们经常使用无界循环,这会给我们带来更多复杂性,但 Java 的函数式 API 可以轻松处理,我们将在下一节中看到。
带 break 语句的无界迭代
让我们看看以下命令式风格的循环,它除了步长之外,还是无界的,并使用 break 语句。
for(int i = 0;; i = i + 3) {
if(i > 20) {
break;
}
System.out.println(i);
}
i < 15 的终止条件消失了,循环是无界的,如重复的 ;; 所示。但是,在循环内,我们有 break 语句,如果 i 的值大于 20,则退出迭代。
对于函数式风格,我们可以删除第二个参数,即 iterate() 方法调用中的 IntPredicate,但这会将迭代变成一个无限流。命令式风格 break 的函数式编程等效项是 takeWhile() 方法。此方法将在传递给它的 IntPredicate 评估为 false 时终止内部迭代器,即流。让我们将之前的命令式风格无界 for 循环重构为函数式风格。
IntStream.iterate(0, i -> i + 3)
.takeWhile(i -> i <= 20)
.forEach(System.out::println);
iterate() 方法是重载的,有两种形式,一种带 IntPredicate,另一种不带。我们使用了不带谓词的版本来创建一个从种子或起始值生成值的无限流。作为第二个参数传递的 IntUnaryOperator 确定步长。因此,在给定的代码示例中,流将生成值 0、3、6 等。由于我们希望限制迭代,以便索引不超过 20 的值,因此我们使用 takeWhile()。传递给 takeWhile() 的谓词表明,只要给定的参数值,即索引 i,不超过 20 的值,迭代就可以继续。
我们在上一篇文章中看到,range() 和 rangeClosed() 是简单 for 循环的直接替换。如果循环变得更复杂,不用担心,Java 为您提供了保障,您可以使用 IntStream 的 iterate() 方法,如果循环使用 break 语句,还可以选择使用 takeWhile() 方法。takeWhile() 是命令式风格 break 的函数式等效项。
映射
在任何看到带步长的 for 循环的地方,都使用带三个参数的 iterate() 方法,即种子或起始值、用于终止条件的 IntPredicate 和用于步长的 IntUnaryOperator。如果您的循环使用 break 语句,则从 iterate() 方法调用中删除 IntPredicate,而是使用 takeWhile() 方法。takeWhile() 是命令式风格 break 的函数式等效项。
上次更新: 2023 年 7 月 6 日