目录
一、增强for
1.为什么需要增强for循环?
在某些情况下,常规的遍历方式容易显得代码臃肿,增强for可以简化数组和集合的遍历,增强代码的可读性。
2.增强for格式:
for (数据类型变量名 : 数组或者集合对象) {
//循环体
}
Δ注意 :
①数据类型即遍历对象中元素的数据类型。比如遍历集合,数据类型就是Object类型,遍历数组,可以int类型,double类型等。
②此处的变量即元素。
3.IDEA中增强for的快捷键:
实际开发中,常常使用增强for的快捷键。输入iter + 回车。
注意 : 快捷键只能生成增强for的外围格式,里面具体的循环体要自己写。
Δ联系(原理):
为什么快捷键是iter?
这是因为增强for的底层依赖的是迭代器(iterator),迭代器下文讲到。
即可以理解为:增强for就是迭代器的简写形式。
Δ快捷键展示:
如下gif图:
4.代码演示:
Δ准备工作:
假设我们想添加Student类型的元素到集合中,我们需要先创建一个Student类,并且需要重写toString() 方法,以直观地打印出学生对象。如图,up在forex包下创建了一个Student类。
Student类代码如下:
packageknowledge.API.gather.forex; publicclassStudent { //私有的成员变量privateStringname; privateintage; //公共的空参构造publicStudent() { } //公共的带参构造publicStudent(Stringname, intage) { this.name=name; this.age=age; } //getter,setter方法publicStringgetName() { returnname; } publicvoidsetName(Stringname) { this.name=name; } publicintgetAge() { returnage; } publicvoidsetAge(intage) { this.age=age; } //重写toString() 方法,以便于遍历集合时可以打印出学生对象的基本信息。publicStringtoString() { return"Student{"+"name='"+name+'\''+", age="+age+'}'; } }
Δ代码:
接着up还是在forex包下创建一个EnhancedFor类作为演示类,如图:
EnhancedFor类代码如下 :
packageknowledge.API.gather.forex; importjava.util.ArrayList; importjava.util.List; publicclassEnhancedFor { publicstaticvoidmain(String[] args) { //requirement : 展示增强for的功能//使用集合四部曲://1.创建集合对象Listlist=newArrayList(); //之后将Student类型添加到list集合中Listlist2=newArrayList(); //之后将int类型添加到list2集合中。//2.创建元素对象Studentstudent1=newStudent("Big", 18); Studentstudent2=newStudent("Cyan", 20); //3.将元素对象添加到集合对象中list.add(student1); list.add(student2); list.add(student2); /** 注意此时添加的元素均为Object类型 *///4.遍历集合//用增强for循环遍历//以往我们遍历集合,使用普通for循环,如下:for (inti=0; i<list.size(); ++i) { System.out.println("集合list中,索引为"+i+"的元素是:"+list.get(i)); } System.out.println("-----------------------------------------"); //现在我们用增强for遍历,如下:for (Objecto : list) { Studentstudent= (Student) o; //此处,集合list中只有三个Student类型的元素,因此我们可以进行向下转型。System.out.println(student); } System.out.println("-----------------------------------------"); //其实直接输出不转型也可以for (Objecto : list) { System.out.println(o); } //对于list2集合也是同理,//先向集合list2中添加几个整型变量,会自动装箱,变成Integer类型list2.add(11); list2.add(8); list2.add(24); //直接使用增强for遍历吧,如下:/*o是集合中的元素,其本身应该为Integer类型,只不过在添加到集合中时,向上转型为了Object类型,因此遍历时可以进行类型转换。比如下面,转换成Integer类型后,就可以使用Integer类中的方法了,如下 :*/for (Objecto : list2) { Integerii= (Integer) o; //试试Integer中的方法intiii=ii.intValue(); //返回integer的int类型值System.out.println("The value of ii is "+ii); System.out.println("The value of iii is "+iii); } System.out.println("-----------------------------------------"); //直接输出不转型for (Objecto : list2) { System.out.println(o); } //PS : 增强for遍历数组int[] array= {1, 2, 33, 44, 44, 211}; System.out.println("遍历一下数组array : "); for (inti : array) { System.out.println(i); } } }
输出结果:
5.增强for的优缺点:
①优点 :
操作方便,上手容易;
简化代码,简洁优雅。
②缺点:
使用增强for时,无法获取当前元素下标。
使用增强for只能从头到尾地遍历数组或者集合,而不能只遍历一部分。
二、迭代器
1.为什么需要迭代器:
迭代,指对某一具体过程的重复。
迭代器是遍历Collection集合的通用方式。而且列表迭代器还可以做到在遍历集合的同时进行元素的添加、删除等操作。
GOF给迭代器模式的定义为: 提供一种方法访问一个容器(container) 对象中的各个元素,同时又不需暴露该对象的内部细节。可以说,迭代器模式,就是为容器而生。
注意:
迭代器不是容器,而是用来遍历容器的一种工具。
2.迭代器的常用方法:
①hasnext() : 如果集合中仍有元素可以迭代,则返回true。
②next() : 返回迭代的下一个元素对象。
③remove() : 返回值类型为void,删除迭代器当前指向的元素。
3.迭代器的使用方式(步骤):
①根据集合对象获取对应的迭代器对象。
使用iterator() 方法来获取迭代器对象,eg:
Listlist=newArraylist(); Iteratoriter=list.iterator();
注意 :
集合对象每次调用iterator() 方法都会得到一个全新的迭代器对象,默认指向集合的第一个元素之前。
②判断迭代器中是否有元素可以迭代。
③如果有就用next() 方法获取元素。
4.迭代器的执行原理:
①原理:
//以下为演示代码,仅用作举栗讲解,并不完整Iteratoriterator=list.iterator(); while (iterator.hasNext()) { Objecto=iterator.next(); System.out.println(o); } //PS : 可使用快捷键"itit"来快速生成此while循环格式
如以上代码所示,因为要重复判断集合中是否还有元素可以遍历,所以利用while循环, 如果迭代器中有元素,就一直迭代(遍历)。
当获得迭代器对象后,迭代器对象相当于一个引用(指针),该引用指向容器第一个元素的上方,每当hasNext() 判断出集合中有元素可迭代时,就可以继续执行next() 方法,next()方法会将指针下移,并返回下移到的位置上的元素,返回后再次调用hasNext(),如果元素仍存在,next()会继续被执行。直到hasNext()判断出容器中没有元素了,则不执行next()方法。此时如果手动继续执行next() 会报异常NoSuchElementException。
②图示:
③代码:
用代码给大家演示一下,假如我们在循环结束后再次调用next() 方法会造成NoSuchElementException异常。up先在iter包下创建一个Principle类作为演示类,如图:
Principle类代码如下:
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.Iterator; importjava.util.List; publicclassPrinciple { publicstaticvoidmain(String[] args) { //集合四部曲://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象Studentst1=newStudent("张三", 13); Studentst2=newStudent("李四", 18); Studentst3=newStudent("王五", 20); //3.将元素对象添加到集合对象中list.add(st1); list.add(st2); list.add(st3); //4.遍历集合Iteratorite=list.iterator(); System.out.println("用迭代器ite第一次遍历集合如下:"); while (ite.hasNext()) { Objecto=ite.next(); System.out.println(o); } System.out.println("------------------------------------"); System.out.println("伟大的ite迭代器,直接告诉我该集合中还有没有元素能遍历?"+ite.hasNext()); System.out.println("既然如此,再次使用ite遍历该集合可以吗?"); while (ite.hasNext()) { System.out.println(ite.next()); } if (!ite.hasNext()) { System.out.println("打印出这句话时,说明不可以使用同一迭代器再次遍历!"); } } } /*Summary :因此,如果已经使用某一迭代器遍历了集合,想再次用迭代器遍历集合需要获取一个新的迭代器对象。因为旧的迭代器对象hasNext() 方法判断永远为假,因此无法遍历。*/
输出结果:
此时,如果我们在第一个while循环结束后再次调用next() 方法,IDEA会报NoSuchElementException异常,如下GIF图所示 :
5.代码演示:
注释同样重要。
①迭代器常用方法演示:
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.Iterator; //别忘了导包!importjava.util.List; /*注意 :若把演示类的类名写作Iterator,因为Iterator本身是java.util包下的一个接口,所以为了区分,IDEA会自动在Iterator前面加上java.util.的前缀,如果手动删去该前缀,IDEA会认为你用来接收的Iterator是演示类,而不是接口,类型不匹配,会导致报错。*/publicclassIteratorShow { publicstaticvoidmain(String[] args) { //requirement ://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象//3.将元素对象添加到集合对象中list.add("We "); list.add("love "); list.add("programming "); //4.遍历集合(使用迭代器遍历)//迭代器的用法(测试hasNext()方法和next()方法)//1>根据集合对象获取其对应的迭代器对象Iteratoriterator1=list.iterator(); //2>判断迭代器中是否有元素/*因为要重复判断集合中是否还有元素可以遍历,所以利用while循环,如果迭代器中有元素,就一直迭代(遍历)*/while (iterator1.hasNext()) { //3>如果有就获取元素//正常写法 :Object o = ite.next();//因为此时迭代器中的元素都是字符串,所以可直接向下转型,如下:Stringss= (String) iterator1.next(); System.out.println(ss); } System.out.println("----------------------"); //测试一下remove()方法Iteratoriterator2=list.iterator(); while (iterator2.hasNext()) { Stringss= (String) iterator2.next(); if ("We ".equals(ss)) { iterator2.remove(); } } System.out.println("删除掉\"We \"的集合为:"); for (Objecto : list) { System.out.println(o); } } } /*补充 :1.此处的remove()方法是专指迭代器中的remove()方法,而不是集合中的remove()方法2.以下两种情况会报异常IllegalStateException①还未调用next()方法就调用remove()方法②在上一次调用next()方法后已经调用过remove()方法,此时再次调用remove()方法*/
运行结果 :
②并发修改异常演示:
在迭代器常用方法的代码演示中,我们成功利用迭代器遍历出了"We love programming " ,但现在up想整点儿不一样的,比如,在迭代器遍历中加入一个判断条件,如果集合中存在"programming "元素,我们就在它的后面添加一个"forever!"字符串的元素。up以IteratorEX类作为演示类,IteratorEX类代码如下:
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.Iterator; importjava.util.List; publicclassIteratorEX { publicstaticvoidmain(String[] args) { //requirement : 如果集合中存在"programming "元素,就在其后面添加一个字符串"forever!"//集合使用四部曲 ://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象//......//3.将元素对象添加到集合对象中list.add("We "); list.add("love "); list.add("programming "); //4.遍历集合//使用迭代器进行遍历//1>根据集合对象获取对应的迭代器对象Iteratoriterator=list.iterator(); //2>判断迭代器中是否有元素while (iterator.hasNext()) { //3>如果有就获取元素StringsTemp= (String) iterator.next(); if ("programming ".equals(sTemp)) { //这么写可以规避空指针异常。list.add(list.indexOf("programming ") +1, "forever!"); } System.out.println(sTemp); } } }
运行结果:
哎哟我去,看着是那么一回事,可这运行结果咋这样儿了呢?
出现问题及原因 :
1) IDEA会报 并发修改异常(ConcurrentModificationException)
2) 并发修改异常,是因为普通的迭代器对象不可以在遍历集的同时进行集合中元素的增删等操作。 就像人不能一边吃一边拉一样,迭代器它也受不了! 但就像人类发明了马桶,实现了边吃边拉。 迭代器中也有翘楚者,实现这种需求 需要用到列表迭代器!
③列表迭代器演示:
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.List; importjava.util.ListIterator; /**注意:1.普通迭代器无法在遍历的同时进行元素的增删等操作。列表迭代器是 List体系 特有的遍历方式,可以在对集合遍历的同时进行添加,删除等操作。2.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator() 方法,那么所有实现了Collection接口的集合类都有一个iterator() 方法,用以返回一个实现了Iterator接口的对象,即返回一个迭代器对象。*/publicclassListIteratorShow { publicstaticvoidmain(String[] args) { //requirement : 利用迭代器遍历集合,并进行判断,若集合中含有"programming "元素,就在其后面添加一个字符串"forever!"//使用集合四部曲://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象//......创个der//3.将元素对象添加到集合对象中list.add("We "); list.add("love "); list.add("programming "); //4.遍历集合//使用迭代器遍历//1>根据集合对象获取其对应的迭代器对象ListIteratorlistIterator=list.listIterator(); //2>判断迭代器中是否有元素System.out.println("===========打印旧集合:==========="); while (listIterator.hasNext()) { //3>如果有就获取元素StringsTemp= (String) listIterator.next(); if ("programming ".equals(sTemp)) { //一般来说,常量与变量比较时,常量在前,避免了空指针异常/*list.add("forever!");注意,如果使用平时list集合的方法,仍然会报出并发修改异常,此处我们需要调用列表迭代器中的方法。*///对IteratorEX类代码的改进, 使用列表迭代器listIterator.add("forever!"); } System.out.println(sTemp); } //打印新的集合中的值System.out.println("===========打印新集合:==========="); for (Objecto : list) { System.out.print(o); } } }
输出结果:
列表迭代器成功实现了我们的需求!
④两种常见的错误写法演示:
错误写法一:
有些同学可能会冒出一个奇怪的想法:判断集合中是否还有元素可以遍历,我也可以不用hasNext()方法呀,我利用一个判断:iterator.next() != null 代替hasNext()方法难道不可行吗?
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.Iterator; importjava.util.List; publicclassTwoWrongWay { publicstaticvoidmain(String[] args) { //requirement : 展示迭代器常见的两种错误写法 ://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象//3.将集合对象添加到元素对象中list.add(11); list.add(10); list.add(10); list.add(2); list.add(211); list.add(985); list.add(23); list.add(5); //4.遍历集合//常见错误写法一:Iteratoriterator=list.iterator(); while (iterator.next() !=null) { System.out.println(iterator.next()); } //输出结果一: 跳着输出,且报异常NoSuchElementException(两个next()总有一个会指向不存在的元素)//原因 : 即迭代器的执行原理,next()方法达到的两种效果。 } }
运行效果1:
如图所示 ,可以发现错误写法一的运行效果是,集合中的元素是跳着输出的,而且最后还报出了异常NoSuchElementException。原因:每执行一次while循环,都会运行两次next()方法,而每次调用next()方法,都会使迭代器对象引用的指向下移一位(并返回下移后该位置的元素),这就使得在输出的next()方法前,判断中的next()方法已经移动了一位指针,所以最后的输出是跳着输出的。
而最后为什么会报出NoSuchElementException(没有该元素异常),原因:两个next()方法中终究会有一个next方法使得引用的指向指在最后一个元素的下面,从而造成异常。 PS:当集合中一共存在奇数个可遍历元素时,是输出语句中的next()方法造成的异常,当集合中一共存在偶数个可遍历元素时,是判断语句中的next()方法造成的异常。
错误写法二:
有些同学想着省略接收迭代器对象的步骤(Iterator iterator2 = list.iterator();),而是直接利用集合的iterator()方法 来代替其中的iterator2迭代器对象。那我们看看情况如何吧😁
packageknowledge.API.gather.iter; importjava.util.ArrayList; importjava.util.Iterator; importjava.util.List; publicclassTwoWrongWay { publicstaticvoidmain(String[] args) { //requirement : 展示迭代器常见的两种错误写法 ://1.创建集合对象Listlist=newArrayList(); //2.创建元素对象//3.将集合对象添加到元素对象中list.add(11); list.add(10); list.add(10); list.add(2); list.add(211); list.add(985); list.add(23); list.add(5); //4.遍历集合//常见错误写法二:while (list.iterator().hasNext()) { System.out.println(list.iterator().next()); } } }
运行效果2:(GIF图)
诺,运行效果就是,IDEA会不停地输出集合中的第一个元素,而且是永无止境地输出下去,至死不渝!
原因:
每次调用iterator() 方法都会返回一个新的迭代器对象。 所以每次执行while循环的条件语句时,就生成一个新的迭代器对象,而新的迭代器对象 又指向了集合第一个元素的上方,从头开始判断,因此while循环的条件语句相当于重言式, 永远是成立的。
而每次循环生成的另一个新的迭代器对象调用next() 方法又将指针下落到第一个元素, 并返回第一个元素的值。
总结:
其实迭代器的执行原理能看懂,这两种常见错误的原因也肯定能看懂。
感谢阅读!