Java 8 习惯用语,第 3 部分 传统 for 循环的函数式替代方案
2017-12-07 15:11
656 查看
原文地址:https://www.ibm.com/developerworks/cn/java/j-java8idioms3/index.html
Java 8 习惯用语,第 3 部分
3 个消除复杂迭代中的麻烦的新方法
Venkat
Subramaniam
2017 年 6 月 05 日发布
WeiboGoogle+用电子邮件发送本页面
1
尽管
Java 9 中引入)。
关于本系列
Java 8 是自 Java 语言诞生以来进行的一次最重大更新 — 包含了非常丰富的新功能,您可能想知道从何处开始着手了解它。在本系列中,作家兼教师 Venkat Subramaniam 提供了一种惯用的 Java 8 编程方法:这些简短的探索会激发您反思您认为理所当然的 Java 约定,同时逐步将新技术和语法集成到您的程序中。
在 Java 语言的第 1 个版本中就开始引入了传统的
Java 5 中引入的。大部分开发人员更喜欢使用
清单 1. 完成一个简单任务的复杂代码
在清单 1 中,我们从 1 开始循环处理索引变量
清单 1 中没有太多代码,但比较繁琐。Java 8 提供了一种更简单、更优雅的替代方法:
1 中的相同
清单 2. 完成一个简单任务的简单代码
在清单 2 中,我们看到并没有显著减少代码量,但降低了它的复杂性。这样做有两个重要原因:
不同于
迭代会自动执行,所以我们不需要像循环索引一样定义增量。
在语义上,最初的
清单 3 中的
清单 3. 在内部类中使用索引变量
我们有一个匿名的内部类实现了
作为此限制的解决办法,我们可以创建一个局部临时变量,比如
Java 8 以前,我们需要将该变量标记为
现在尝试使用
清单 4. 在内部类中使用拉姆达参数
在作为一个参数被拉姆达表达式接受后,索引变量
— 且不会有任何麻烦。
因为
清单 5. 将内部类替换为拉姆达表达式
显然,对于相对简单的迭代,使用
Java 8 方法孰优孰劣。
创建
清单 6. 一个具有封闭范围的 for 循环
索引变量
清单 7. rangeClosed 方法
迭代此范围时,我们会获得包含边界值 5 在内的值。
对于基本循环,
8 中,
清单 8. 使用 for 跳过值
清单 8 中的循环在 1 到 100 内对每次读到的第三个值作求和计算 — 这种复杂运算可使用
首先,可以考虑使用
清单 9. 使用 limit 的迭代
8,我们将一个表达式传递给
如何解决这个问题?
我们对 1 到 100 之间的值感兴趣,而且想从 1 开始跳过两个值。稍加运算,即可确定给定范围中有 34 个符合要求的值。所以我们将该数字传递给
此代码很有效,但过程太复杂:提前执行数学运算不那么有趣,而且它限制了我们的代码。如果我们决定跳过 3 个值而不是 2 个值,该怎么办?我们不仅需要更改代码,结果也很容易出错。我们需要有一个更好的方法。
Java 9 中即将引入的
9 中的迭代的代码。
清单 10. 有条件的迭代
无需将迭代限制到预先计算的次数,我们使用提供给
与
JDK 中非常需要的补充方法。
continue。从 Java 9 开始,它们将可用于任何类型的
与正向迭代相比,逆向迭代同样非常简单,无论使用传统的
以下是一个逆向的
清单 11. 使用 for 的逆向迭代
清单 12. 使用 iterate 的逆向迭代
将一个拉姆达表达式作为参数传递给
尽管传统
Java 的一个长期以来的要求,那就是局部变量必须声明为 final,然后才能从内部类访问它。将一个可变索引变量更换为实际的 final 参数只有很小的语义差别,但它减少了大量垃圾变量。最终您会得到更简单、更优雅的代码。
Java 中的函数式编程:The Pragmatic Bookshelf,2014 年
Java Streams,第 1 部分:java.util.stream
简介
Java 集合:值得留意的可变变量
Java 8
中的集合迭代
Java 8 习惯用语,第 3 部分
传统 for 循环的函数式替代方案
3 个消除复杂迭代中的麻烦的新方法Venkat
Subramaniam
2017 年 6 月 05 日发布
WeiboGoogle+用电子邮件发送本页面
1
系列内容:
此内容是该系列 9 部分中的第 3 部分: Java
8 习惯用语
尽管 for循环包含许多可变部分,但许多开发人员仍非常熟悉它,并会不假思索地使用它。从 Java™ 8 开始,我们有多个强大的新方法可帮助简化复杂迭代。在本文中,您将了解如何使用
IntStream方法
range、
iterate和
limit来迭代范围和跳过范围中的值。您还将了解新的
takeWhile和
dropWhile方法(即将在
Java 9 中引入)。
关于本系列
Java 8 是自 Java 语言诞生以来进行的一次最重大更新 — 包含了非常丰富的新功能,您可能想知道从何处开始着手了解它。在本系列中,作家兼教师 Venkat Subramaniam 提供了一种惯用的 Java 8 编程方法:这些简短的探索会激发您反思您认为理所当然的 Java 约定,同时逐步将新技术和语法集成到您的程序中。
for 循环的麻烦
在 Java 语言的第 1 个版本中就开始引入了传统的 for循环,它的更简单的变体
for-each是在
Java 5 中引入的。大部分开发人员更喜欢使用
for-each执行日常迭代,但对于迭代一个范围或跳过范围中的值等操作,他们仍会使用
for。
for循环非常强大,但它包含太多可变部分。甚至在打印
get set提示的最简单任务中,也可以看出这一点:
清单 1. 完成一个简单任务的复杂代码
i,将它限制到小于 4 的值。请注意,
for循环需要我们告诉循环是递增的。在本例中,我们还选择了前递增而不是后递增。
清单 1 中没有太多代码,但比较繁琐。Java 8 提供了一种更简单、更优雅的替代方法:
IntStream的
range方法。以下是打印清单
1 中的相同
get set提示的
range方法:
清单 2. 完成一个简单任务的简单代码
不同于
for,
range不会强迫我们初始化某个可变变量。
迭代会自动执行,所以我们不需要像循环索引一样定义增量。
在语义上,最初的
for循环中的变量
i是一个可变变量。理解
range和类似方法的价值对理解该设计的结果很有帮助。
可变变量与参数
for循环中定义的变量
i是单个变量,它会在每次对循环执行迭代时发生改变。
range示例中的变量
i是拉姆达表达式的参数,所以它在每次迭代中都是一个全新的变量。这是一个细微区别,但决定了两种方法的不同。以下示例有助于阐明这一点。
清单 3 中的
for循环想在一个内部类中使用索引变量:
清单 3. 在内部类中使用索引变量
Runnable接口。我们想在
run方法中访问索引变量
i,但编译器不允许这么做。
作为此限制的解决办法,我们可以创建一个局部临时变量,比如
temp,它是索引变量的一个副本。每次新的迭代都会创建变量
temp。在
Java 8 以前,我们需要将该变量标记为
final。从 Java 8 开始,可以将它视为实际的最终结果,因为我们不会再更改它。无论如何,由于事实上索引变量是一个在迭代中改变的变量,
for循环中就会出现这个额外变量。
现在尝试使用
range函数解决同一个问题。
清单 4. 在内部类中使用拉姆达参数
i的语义与循环索引变量有所不同。与清单 3 中手动创建的
temp非常相似,这个
i参数在每次迭代中都表现为一个全新的变量。它是实际最终变量,因为我们不会在任何地方更改它的值。因此,我们可以直接在内部类的上下文中使用它
— 且不会有任何麻烦。
因为
Runnable是一个函数接口,所以我们可以轻松地将匿名的内部类替换为拉姆达表达式,比如:
清单 5. 将内部类替换为拉姆达表达式
range代替
for具有一定优势,但
for的特殊价值体现在于它能处理更复杂的迭代场景。让我们看看
range和其他
Java 8 方法孰优孰劣。
封闭范围
创建 for循环时,可以将索引变量封闭在一个范围内,比如:
清单 6. 一个具有封闭范围的 for 循环
i接受值
0、
1、……
5。无需使用
for,我们可以使用
rangeClosed方法。在本例中,我们告诉
IntStream将最后一个值限制在该范围内:
清单 7. rangeClosed 方法
跳过值
对于基本循环,range和
rangeClosed方法是
for的更简单、更优雅的替代方法,但是如果想跳过一些值该怎么办?在这种情况下,
for对前期工作的需求使该运算变得非常容易。在清单
8 中,
for循环在迭代期间快速跳过两个值:
清单 8. 使用 for 跳过值
for轻松完成。能否也使用
range解决此问题?
首先,可以考虑使用
IntStream的
range方法,再结合使用
filter或
map。但是,所涉及的工作比使用
for循环要多。一种更可行的解决方案是结合使用
iterate和
limit:
清单 9. 使用 limit 的迭代
iterate方法很容易使用;它只需获取一个初始值即可开始迭代。作为第二参数传入的拉姆达表达式决定了迭代中的下一个值。这类似于清单
8,我们将一个表达式传递给
for循环来递增索引变量的值。但是,在本例中有一个陷阱。不同于
range和
rangeClosed,没有参数来告诉
iterate方法何时停止迭代。如果我们没有限制该值,迭代会一直进行下去。
如何解决这个问题?
我们对 1 到 100 之间的值感兴趣,而且想从 1 开始跳过两个值。稍加运算,即可确定给定范围中有 34 个符合要求的值。所以我们将该数字传递给
limit方法。
此代码很有效,但过程太复杂:提前执行数学运算不那么有趣,而且它限制了我们的代码。如果我们决定跳过 3 个值而不是 2 个值,该怎么办?我们不仅需要更改代码,结果也很容易出错。我们需要有一个更好的方法。
takeWhile 方法
Java 9 中即将引入的 takeWhile是一个新方法,它使得执行有限制的迭代变得更容易。使用
takeWhile,可以直接表明只要满足想要的条件,迭代就应该继续执行。以下是使用
takeWhile实现清单
9 中的迭代的代码。
清单 10. 有条件的迭代
takeWhile的条件,动态确定何时终止迭代。与尝试预先计算迭代次数相比,这种方法简单得多,而且更不容易出错。
与
takeWhile方法相反的是
dropWhile,它跳过满足给定条件前的值,这两个方法都是
JDK 中非常需要的补充方法。
takeWhile方法类似于 break,而
dropWhile则类似于
continue。从 Java 9 开始,它们将可用于任何类型的
Stream。
逆向迭代
与正向迭代相比,逆向迭代同样非常简单,无论使用传统的 for循环还是
IntStream。
以下是一个逆向的
for循环迭代:
清单 11. 使用 for 的逆向迭代
range或
rangeClosed中的第一个参数不能大于第二个参数,所以我们无法使用这两种方法来执行逆向迭代。但可以使用
iterate方法:
清单 12. 使用 iterate 的逆向迭代
iterate方法,该方法对给定值进行递减,以便沿相反方向执行迭代。我们使用
limit函数指定我们希望在逆向迭代期间看到总共多少个值。如有必要,还可以使用
takeWhile和
dropWhile方法来动态调整迭代流。
结束语
尽管传统 for循环非常强大,但它有些过于复杂。Java 8 和 Java 9 中的新方法可帮助简化迭代,甚至是简化复杂的迭代。方法
range、
iterate和
limit的可变部分较少,这有助于提高代码效率。这些方法还满足了
Java 的一个长期以来的要求,那就是局部变量必须声明为 final,然后才能从内部类访问它。将一个可变索引变量更换为实际的 final 参数只有很小的语义差别,但它减少了大量垃圾变量。最终您会得到更简单、更优雅的代码。
相关主题
Java 中的函数式编程:The Pragmatic Bookshelf,2014 年Java Streams,第 1 部分:java.util.stream
简介
Java 集合:值得留意的可变变量
Java 8
中的集合迭代
相关文章推荐
- Java 8 习惯用语,第 5 部分 传递表达式(pass-through lambdas)的替代方案
- Java 8 新语法习惯 (for 循环的函数替代方案)
- Java 8 习惯用语,第 1 部分 Java 中的一种更轻松的函数式编程途径
- Java 8 习惯用语,第 7 部分 函数接口
- Effective Java(2nd Edition) Item 46 for-each循环优于传统的for循环(译文)
- Java 8 习惯用语,第 6 部分 为什么完美的 lambda 表达式只有一行
- Effective Java 学习笔记(第46条:for-each循环优先于传统的for循环)
- Java 8 习惯用语,第 2 部分 函数组合与集合管道模式
- Effective Java 之-----for-each循环优于传统的for循环
- Java高效编码:for-each循环优先于传统的for循环
- Java高效编码:for-each循环优先于传统的for循环
- Java 8 新语法习惯 (传递表达式的替代方案)
- Java 8 习惯用语,第 8 部分 Java 知道您的类型
- Java 8 习惯用语,第 4 部分 提倡使用有帮助的编码
- Java 8 习惯用语,第 9 部分 级联 lambda 表达式
- JAVA-for循环语句
- 黑马程序员_java学习日记_Java高新技术静态导入,可变参数和加强的FOR循环
- JavaScript传统for循环实例
- Java中的for循环和JavaScript中的for循环差别初探(01)
- 【Java部分源码分析之io篇】0.传统IO输入输出流总览