for 循环用了那么多次,但你真的了解它么?
其实我们写代码的时候一直都在使用for循环,但是偶尔还是会纠结用哪一个循环。
一、基础的for循环
0、使用while也是一种循环方式,此处探究for相关的循环,就不做拓展了。
1、遍历数组的时候,初学时是使用的如下样式的for循环:
for(int i=0;i<a.length;i++){
System.out.println(n);
}
2、而遍历集合的时候使用的都是Iterator迭代器:
给定一组人名,两两组队(此处允许自己和自己组队),实现如下:
enum Option {Tom, Jerry, Jack, Mary}
想象中的写法是:
Collection<Option> options = Arrays.asList(Option.values());
for(Iterator<Option> i = options.iterator(); i.hasNext();){
for (Iterator<Option> j = options.iterator(); j.hasNext();) {
System.out.println(i.next()+" "+j.next());
}
但是执行过后你会发现这段代码是有瑕疵的,出现的结果只有四组:
那么剩下的组合去哪里了呢?
这里程序并不会抛出异常,只是单纯的因为i.next()
每次都会取下一个值,所以就出现了上图的情况。
但是,如果外部集合的元素大于内部元素:
例如下面这段代码:
enum OptionFirst {Tom, Jerry, Jack, Mary}
enum OptionSecond {Tom, Jerry, Jack, Mary, Mali, Tomsun, Lijie, Oolyyou} public static void main(String[] args) {
Collection<OptionFirst> optionFirstCollection = Arrays.asList(OptionFirst.values());
Collection<OptionSecond> optionSecondCollection = Arrays.asList(OptionSecond.values());
for (Iterator<OptionFirst> i = optionFirstCollection.iterator(); i.hasNext(); ) {
for (Iterator<OptionSecond> j = optionSecondCollection.iterator(); j.hasNext(); ) {
System.out.println(i.next() + " " + j.next());
}
}
}
运行后,就会抛出java.util.NoSuchElementException
异常,造成的原因就是因为外部循环调用了多次,而内部循环因为元素不足,导致循环抛出了这样的异常。
要想解决这种困扰只需要在二次循环前添加一个变量来保存外部元素;即可实现想要达到的效果。
Collection<Option> options = Arrays.asList(Option.values());
for(Iterator<Option> i = options.iterator(); i.hasNext();){
Option option = i.next();
for (Iterator<Option> j = options.iterator(); j.hasNext();) {
System.out.println(option+" "+j.next());
}
}
二、for-each循环
这种循环方式不论是数组还是集合都实用,而且效率更高;表达形式更加简洁明了。
for(Element e:elements){
System.out.println(e);
}
当再次遇到上面的两两组队问题时,根本不需要考虑元素不足的问题,而且代码也简洁多了:
for (Option option : options) {
for (Option rank : options) {
System.out.println(option + " " + rank);
}
}
《Effective Java》中是这样子写for-each循环的:
三、for-each is not god
说了for-each那么多好处,但是它也不是神,并非万能的,有那么三种情况是它需要注意的。
3.1、解构过滤的时候不能使用
如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便可以调用它的remove方法。不过在Java8中增加的Collection的removeIf方法常常可以避免显式的遍历。
例如下面这段代码:
List<String> list = new ArrayList<String>(); list.add("1");
list.add("2"); for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
System.out.println("执行if语句成功");
}
}
直接运行这段代码是没什么问题的,数组list能成功删除元素1,也能打印对应语句。
但是,我们进行如下任意一种操作:
- 若把list.remove(item)换成list.add(“3”);操作如何?
- 若在第6行添加list.add("3");那么代码会出错吗?
- 若把if语句中的“1”换成“2”,结果你感到意外吗?
如果都能正确执行当然就不需要问了,所以3个都会报ConcurrentModificationException的异常;
而出现这些情况的原因稍稍解释下就是:
首先,这涉及多线程操作,Iterator是不支持多线程操作的,List类会在内部维护一个modCount的变量,用来记录修改次数。
举例:ArrayList源码
protected transient int modCount = 0;
每生成一个Iterator,Iterator就会记录该modCount,每次调用next()方法就会将该记录与外部类List的modCount进行对比,发现不相等就会抛出多线程编辑异常。
为什么这么做呢?我的理解是你创建了一个迭代器,该迭代器和要遍历的集合的内容是紧耦合的,意思就是这个迭代器对应的集合内容就是当前的内容,我肯定不会希望在我冒泡排序的时候,还有线程在向我的集合里插入数据对吧?所以Java用了这种简单的处理机制来禁止遍历时修改集合。
至于为什么删除“1”就可以呢,原因在于foreach和迭代器的hasNext()方法,foreach这个语法,实际上就是
while(itr.hasNext()){
itr.next()
}
所以每次循环都会先执行hasNext(),那么看看ArrayList的hasNext()是怎么写的:
public boolean hasNext() {
return cursor != size;
}
cursor是用于标记迭代器位置的变量,该变量由0开始,每次调用next执行+1操作,于是:
所以代码在执行删除“1”后,size=1,cursor=1,此时hasNext()返回false,结束循环,因此你的迭代器并没有调用next查找第二个元素,也就无从检测modCount了,因此也不会出现多线程修改异常;但当你删除“2”时,迭代器调用了两次next,此时size=1,cursor=2,hasNext()返回true,于是迭代器傻乎乎的就又去调用了一次next(),因此也引发了modCount不相等,抛出多线程修改的异常。
当你的集合有三个元素的时候,你就会神奇的发现,删除“1”是会抛出异常的,但删除“2”就没有问题了,究其原因,和上面的程序执行顺序是一致的。
因此,在《阿里巴巴Java开发手册中有这样一条规定》:
3.2、转换
如果需要遍历列表或数组,并取代它的部分或者全部元素值,就需要使用列表迭代器或者数组索引,以便替换元素的值。
因为for-each是一循到底的,中间不做停留和位置信息的显示;所以要替换元素就不能使用它了。
3.3、平行迭代
如果需要并行的遍历多个集合,就需要显式的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以同步前进(就像上面讲述Iterator迭代器的时候提到的组合减少的情况,只想出现下标一一对应的元素组合)。
4、总结
for-each循环不仅适用于遍历集合和数组,而且能让你遍历任何实现Iterator接口的对象;最最关键的是它还没有性能损失。而对数组或集合进行修改(添加删除操作),就要用for循环。
所以循环遍历所有数据的时候,能用它的时候还是选择它吧,嘻嘻。
for 循环用了那么多次,但你真的了解它么?的更多相关文章
- while 循环 于 for循环语句
while 循环 while 循环 while 条件: 如果条件是真的,就继续的循环 如果条件是假的,就停止循环 循环的意思就是让程序重复地执行某些语句,whiler循环就是循环结构的一种,当事先不知 ...
- 《Python基础教程(第二版)》学习笔记 -> 第五章 条件、循环 和 其他语句
条件和条件语句 下面的值在作为布尔表达式的时候,会被解释器看作假(False):False None 0 "" () [] {} 条件执行和if语句 ...
- python3之循环
本节主要介绍python中循环语句的用法以及其他一些语句exec等,文章后面附有之前的文章: 循环语句 if是值执行一次而循环时在条件满足情况下可以执行相同语句多次:使用循环可以轻松实现序列,词典等的 ...
- Beego模板 循环和判断几个例子
Beego模板 循环和判断几个例子 Beego的前端几乎是另一种语言.一些循环.判断,不细看文档真的做不出来. 0. Beego的View模板语法规则: beego前端(view)统一使用了 {{ 和 ...
- Xcode断点的一些黑魔法
转自 只会左键断点?是时候试试这样那样断点了 编码不能没调试,调试不能没断点(Break Point).XCode的断点功能也是越来越强大. 基本断点 如下图,这种是最常用的断点,也是最容易设置.左键 ...
- javascript语句语义大全(3)
1. for(var i=0;i<10;i++){ } for循环,括号里面是循环条件,翻译过来是,初始设定1=0:没循环一次i会+1,直到i<10 2. var i=0: while(i ...
- Linux 桌面玩家指南:14. 数值计算和符号计算
特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...
- Python3 与 C# 扩展之~模块专栏
代码裤子:https://github.com/lotapp/BaseCode/tree/maste 在线编程:https://mybinder.org/v2/gh/lotapp/BaseCode ...
- python学习笔记4-内置函数、匿名函数、json处理
print(all([1,2,3,4]))#判断可迭代的对象里面的值是否都为真 print(any([0,1,2,3,4]))#判断可迭代的对象里面的值是否有一个为真 print(bin(10))#十 ...
- 最全Python内置函数
内置函数的基本使用 abs的使用: 取绝对值 absprint(abs(123))print(abs(-123)) result:123123 all的使用: 循环参数,如果每个元素都为真的情况下,那 ...
随机推荐
- JS实现点击查看密码功能,再次点击隐藏密码!
<table border='1'> <tr> <td>aaaa</td> <td onclick="myFunction(this.i ...
- 深入理解计算机系统 第十一章 网络编程 part2 第二遍
客户端和服务器通过因特网这个全球网络来通信.从程序员的观点来看,我们可以把因特网看成是一个全球范围的主机集合,具有以下几个属性: 1.每个因特网主机都有一个唯一的 32 为名字,称为它的 IP 地址 ...
- kafka运维填坑
转载自:https://www.jianshu.com/p/d2cbaae38014 前提: 只针对Kafka 0.9.0.1版本; 说是运维,其实偏重于问题解决; 大部分解决方案都是google而来 ...
- Java进阶开发-基于Base64的加密与解密操作
基于Base64的加密与解密操作 正常来讲加密基本上永远伴随着解密,所谓的加密或者解密往往都是需要有一些所谓的规则.在JDK1.8开始提供有一组新的加密处理操作,Base64处理.在这个类里面有两个内 ...
- O043、计算节点宕机了怎么办
参考https://www.cnblogs.com/CloudMan6/p/5562131.html Rebuild 可以恢复损坏的instance .那如果是宿主机坏了怎么办呢?比如硬件故障或者 ...
- LeetCode:176.第二高的薪水
题目链接:https://leetcode-cn.com/problems/second-highest-salary/ 题目 编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Sal ...
- SQL中 left join 的底层原理
介绍 left join的实现效果就是保留左表的全部信息,将右表往左表上拼接,如果拼不上则为NULL. 除了left join以外,还有inner join.outer join.right join ...
- js之数据类型(对象类型——构造器对象——对象)
JavaScript中除了原始类型,null,undefined之外就是对象了,对象是属性的集合,每个属性都是由键值对(值可以是原始值,比如说是数字,字符串,也可以是对象)构成的.对象又可分为构造器对 ...
- vue项目性能优化总结
在使用elementUI构建公司管理系统时,发现首屏加载时间长,加载的网络资源比较多,对系统的体验性会差一点,而且用webpack打包的vuejs的vendor包会比较大.所以通过搜集网上所有对于vu ...
- 4 java 笔记
1 javadoc工具默认只处理以public,protected修饰的类,接口,方法,成员变量,构造器和内部类之前的文档注释 2 文档注释以斜线后紧跟两个星号开始/**,以星号后紧跟一个斜线结束*/ ...