有个小朋友准备明年跳槽,结果遇到一道题,还以为是一道不可思议的笔试题。然后我把这个问题发到技术群里,发现很多人都不知道,也有很多人在猜测。我觉得有必要写一篇文章来谈谈。奇怪的笔试题阅读下面的代码,请写出这段代码的输出:importjava.util.ArrayList;importjava.util.Iterator;importjava.util.*;publicclassTest{publicstaticvoidmain(String[]args){Listlist=newArrayList<>();list.add("1");list.add("2");list.add("3");Iteratoriterator=list.iterator();while(iterator.hasNext()){Stringstr=(String)iterator.next();if(str.equals("2")){iterator.remove();}}while(iterator.hasNext()){System.out.println(iterator.next());}System.out.println("4");}}他写的答案是:134奇怪的是,你把这个问题发给你身边的人,让他们回答这个面试输出的结果是什么的问题?说这个结果的人很多。不,你试试。答案显然是错误的,因为第一个while中的iterator.hasNext()==false会来到第二个while中。同一个Iterator对象,之前调用一次iterator.hasNext()==false,然后再判断一次结果是不是一样?所以第二个while判断为false,所以不会再遍历iterator,所以可以看出本体的答案是:4、下面分析一下为什么具体底层是怎么实现的。这里的迭代器是什么?迭代器是一种模式,详细看它的设计模式,可以将序列类型数据结构的遍历行为与被遍历的对象分离,即我们不需要关心底层是什么序列的结构看起来像。只要拿到这个对象,就可以使用迭代器遍历这个对象内部的Iterable。实现该接口的集合对象支持迭代,可以迭代。这实现了可以与foreach一起使用的对象,使用~Iterator迭代器提供迭代机制。如何迭代由Iterator接口指定。IteratorDescriptionpublicinterfaceIterator{//在每个next之前,调用该方法检测迭代是否已经结束booleanhasNext();//返回当前迭代元素,同时将迭代光标移回Enext();/*删除最新的一个最后迭代出来的元素。remove函数只能在执行next之后调用。比如要删除第一个元素,不能直接调用remove(),而是先调用next();在不先调用next的情况下调用remove方法将引发异常。这非常类似于MySQL中的ResultSet*/defaultvoidremove(){thrownewUnsupportedOperationException("remove");}defaultvoidforEachRemaining(Consumeraction){Objects.requireNonNull(action);while(hasNext())action.accept(next());}}这里的实现类是ArrayList的内部类Itr。privateclassItrimplementsIterator{intcursor;//indexofnextelementtoreturnintlastRet=-1;//indexoflastelementreturned;-1ifnosuch//modCountshiArrayList属性,moCount值在增删时会增加或减少//这里主要用于fail-fast,避免遍历一次,onetimeismodifyingandcausingdataerrors//这个列表在结构上被修改的次数。结构修改是指一系列修改,这些修改会更改结构的大小,//或以可能导致错误结果的方式扰乱它。expectedModCount=modCount;publicbooleanhasNext(){//游标初始值为0,next方法不掉一次+1//size为ArrayList的大小returncursor!=size;}@SuppressWarnings("unchecked")publicEnext(){checkForCoModification();inti=cursor;if(i>=size)thrownewNoSuchElementException();//将ArrayList中的数组赋值给elementDataObject[]elementData=ArrayList.this.elementData;if(i>=elementData.length)thrownewConcurrentModificationException();//每次调用next方法,游标增加1//cursor=lastRet+1cursor=i+1;//返回ArrayList中的元素return(E)elementData[lastRet=i];}publicvoidremove(){if(lastRet<0)thrownewIllegalStateException();checkForComodification();try{//调用ArrayList中的remove方法溢出元素ArrayList.this.remove(lastRet);//cursor=lastRet+1,//所以这次相当于cursor=cursor-1cursor=lastRet;lastRet=-1;expectedModCount=modCount;}catch(IndexOutOfBoundsExceptionex){thrownewConcurrentModificationException();}}finalvoidcheckForComodification(){if(modCount!=expectedModCount)thrownewConcurrentModificationException();}}然后回到上面的主题:首先是iterator.hasNext()在第一个循环hasNext方法中:cursor==0,size==3,所以cursor!=size返回true在next方法中:cursor=0+1。返回“1”。在第二个循环hasNext方法中:cursor==1,size==3,所以cursor!=size返回true。在下一个方法中:cursor=1+1。返回“2”。remove方法中:cursor==cursor-1==2-1=1,删除了ArrayList中的“2”,所以size==2。在第三个循环的hasNext方法中:cursor==1,size==2,则cursor!=size返回true。在下一个方法中:cursor=1+1==2;返回“3”。在第4个循环的hasNext方法中:cursor==2,size==2,那么cursor!=size返回false。在第二个iterator.hasNext()hasNext方法中:cursor==2,size==2,所以cursor!=size返回false。所以最后只输出“4”,即答案为4。Iterator和泛型搭配Iterator可以返回这样一个Iterator对象给集合类中的任意一个实现类。可以应用于任何班级。因为可以装入集合类(List、Set等)的对象类型是不确定的,从集合中取出来都是Object类型,使用时需要进行转换,会很不方便麻烦。使用泛型就是提前告诉集合判断要加载的集合类型,这样就可以直接使用,不显示类型转换。很方便。foreach和Iteratorforeach的关系是用来处理集合中的每一个元素,而不考虑集合的下标。只是为了使Iterator的使用变得简单。但是在删除的时候,不同的是在remove中,在循环中调用集合remove会导致原来的集合发生变化而报错,但是要使用迭代器的remove方法。使用for循环或者迭代器Iterator进行随机访问比使用ArrayList更快,而for循环中的get()方法使用的是随机访问的方法,所以在ArrayList中,for循环更快而LinkedList是顺序访问更快,iterator中的next()方法采用的是顺序访问的方式,所以在LinkedList中,iterator从数据结构的角度来分析速度更快,for循环适合访问顺序结构,可以根据快速获取指定元素下标。而迭代器适合访问链式结构,因为迭代器是由next()和Pre()定位的。它可以无序访问集合。使用Iterator的好处是可以用同样的方式遍历集合中的元素,而不用考虑集合类的内部实现(只要实现了java.lang.Iterable接口即可),如果使用Iterator遍历集合中的元素,一旦你不再使用List来使用Set来组织数据,那么遍历元素的代码就不需要修改,如果使用for来遍历,那么遍历这个集合的所有算法都得调整相应地,由于List是有序的,Set是无序的,结构不同,它们的访问算法也不同。(它仍然显示出遍历和集合本身有点分离)。总结迭代元素是原始集合元素的副本。Java集合中保存的元素实际上是对对象的引用,而不是对象本身。迭代对象也是引用的副本,结果还是引用。那么如果集合中保存的元素是可变的,那么可以通过迭代元素来修改原始集合中的对象。本文转载自微信公众号《Java后端技术全栈》,可通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。