琐碎的想法(五)for 的前世今生
for 的前世今生
起因
记得大学上C语言的课,第一次遇到的问题就是循环结构里面的 for
。
在此之前,选择结构的 if
非常易懂,和日常生活的判断没有区别。
循环结构的 while
同样比较好理解。
本质上是一个判断
- 如果为真,继续循环。
- 如果不假,则退出循环。
而 for
会不好理解一些。
for (init-expr; test-expr; update-expr)
body-statement
- 初始化表达式只执行一次
- 判断表达式执行
- 判断为真执行循环体
- 判断为假退出循环体
- 执行更新表达式
在实际的语义等同于(唯一区别是init-expr
。 for
的是内部变量,而 while
的是外部变量)
init-expr
while (test-expr) {
body-statement
update-expr
}
那么我们为什么要设计一个没那么好理解的循环结构呢?
因为这时候才入了编程的门——抽象,以及约定。
如果我们再往底层挖,会发现在汇编语言中是不存在while
、for
关键字的。
最开始的程序总是从左到右,从上到下一条路走到黑的。
后面编程人员意识到编写重复的代码过于麻烦才创造了循环结构 loop
。
所以最开始需要人工写一个for或者while循环。
while
好理解在于和自然语言(英语)完全符合。
当 条件满足 时, {
执行 流程;
}
而 for
循环的好处在于规范了 while
的使用。
- 初始化语句(init-expr)一般只用于循环,所以放在内部,便于回收变量。
- 循环条件(test-expr)一般配合更新语句一起使用(update-expr),实现循环有限次数。
- 三者的拆分使编写大段的循环或者嵌套循环时,更易读。
// 传统while循环
int i = 0;
while (i < 10) {
handleX();
i++;
int j = 0;
while (j < 5) {
handleY();
j++;
}
}
// for循环
for (int i = 0; i < 10; i++) {
handleX();
for (int j = 0; j < 5; j++) {
handleY();
}
}
所以 for 循环的出现也意味着编程人员开始在意的不仅仅是功能,而且看重可读性。
然而这并不会被满足。记住:编程人员追求的是简单,再简单。
- 增强
for
,部分语言的for-each,for...in
。 lamdba
表达式中的forEach()
方法。
注意:以下按 Java
实现的 foreach
举例。(其他编程语言不太熟悉)
增强 for
foreach
的规则
- 所有使用
foreach
的集合都必须实现Iterable
接口 - 通过
iterator()
获取iterator
对象 - 通过
iterator.hasNext()
判断是否存在元素。 - 通过
iterator.next()
获取下一个元素。 - 通过
iterator.remove()
移除返回的元素。(可选)
增强 for
的语法
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
// for 版本
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// foreach 版本
for (String e : list) {
System.out.println(e);
}
// 去"糖"后的while版本
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
可以看出,foreach
对于 for、while
来说,好处是更加简单,符合直觉。
- 无需判断集合个数和中间变量减少代码出错可能性,统一通过
hasNext()
处理。 - 不用分析每个集合类如何获取元素,统一通过
next()
处理。 - 通过语法糖隐藏了
hasNext(), next()
逻辑,代码更易读。
forEach()
Java 7/8 受到了函数式语言的影响,实现了更简练的写法。
// Iterable<T> 内实现的forEach
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
// forEach()
list.forEach(i -> System.out.print(i));
// 方法引用
list.forEach(System.out::print);
可以看出,forEach()的好处显而易见。
- 代码量比 增强
for
更少,只关注遍历元素,甚至连元素类型都可以省略。 - 使用了方法引用后更进一步,我们关注的是这个集合执行了哪些操作,遍历每一次的含义在
forEach()
的方法已经体现了,甚至不需要写遍历的元素。
总结
可以看出,编程人员一直追寻的是更简单,更易读的代码。
- 他们不满足于汇编语言一遍遍的写同一行代码,创造了
while
- 不满足于 复杂或多层
while
的不可读, 创造了for
- 不满足于
for
循环每一次定义的中间变量,创造了foreach
- 不满足于
foreach
需要循环每一次的元素,利用了lamdba
的Consumer
, 去掉了元素。
致谢
送给看到最后的读者一句名言,不仅是对待代码,同样也是对待人生。
我愿意深深地扎入生活,吮尽生活的骨髓,过得扎实,简单,把一切不属于生活的内容剔除得干净利落,把生活逼到绝处,用最基本的形式,简单,简单,再简单。
——梭罗《瓦尔登湖》