有位小朋友最近正在为年后换工作做筹备,然而遇到一个问题,感觉很不堪设想的一道口试题。而后我把这道题发到技术群里,发现很多人竟然不晓得,很多都是连蒙带猜的说。感觉很有必要写一篇文章来说道说道。
奇怪的口试题
浏览上面这段代码,请写出这段代码的输入内容:
import java.util.ArrayList;import java.util.Iterator;import java.util.*;public class Test { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { String str = (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。
上面咱们来剖析一下为什么是具体底层是怎么实现的。
这里的Iterator是什么?
- 迭代器是一种模式、具体可见其设计模式,能够使得序列类型的数据结构的遍历行为与被遍历的对象拆散,即咱们无需关怀该序列的底层构造是什么样子的。只有拿到这个对象,应用迭代器就能够遍历这个对象的外部
- Iterable 实现这个接口的汇合对象反对迭代,是能够迭代的。实现了这个能够配合foreach应用~
- Iterator 迭代器,提供迭代机制的对象,具体如何迭代是这个Iterator接口标准的。
Iterator阐明
public interface Iterator<E> { //每次next之前,先调用此办法探测是否迭代到起点 boolean hasNext(); //返回以后迭代元素 ,同时,迭代游标后移 E next(); /*删除最近一次已近迭代出进来的那个元素。 只有当next执行完后,能力调用remove函数。 比方你要删除第一个元素,不能间接调用 remove() 而要先next一下( ); 在没有先调用next 就调用remove办法是会抛出异样的。 这个和MySQL中的ResultSet很相似 */ default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }}
这里的实现类是ArrayList的外部类Itr。
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such //modCountshi ArrayList中的属性,当增加或删除的时候moCount值会减少或者缩小 //这里次要是给fail-fast应用,防止一遍在遍历,一遍正在批改导致数据出错 //此列表在结构上被批改的次数。构造批改是指扭转构造尺寸的批改列表, //或者以这样的形式对其进行扰动,提高可能会产生谬误的后果。 int expectedModCount = modCount; public boolean hasNext() { //cursor初始值为0,没掉一次next办法就+1 //size是ArrayList的大小 return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); //把ArrayList中的数组赋给elementData Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); //每调用一次next办法,游标就加1 //cursor=lastRet+1 cursor = i + 1; //返回ArrayList中的元素 return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { //调用ArrayList中remove办法,溢出该元素 ArrayList.this.remove(lastRet); //cursor=lastRet+1, //所以此时相当于cursor=cursor-1 cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }}
再回到下面题目中:
第一个iterator.hasNext()
第1次循环
- hasNext办法中:cursor==0, size==3,所以cursor != size返回true。
- next办法中:cursor=0+1。返回"1"。
第2次循环
- hasNext办法中:cursor==1, size==3,所以cursor != size返回true。
- next办法中:cursor=1+1。返回"2"。
- remove办法中:cursor==cursor-1==2-1=1,把ArrayList中的"2"给删除了,所以size==2。
第3次循环
- hasNext办法中:cursor==1, size==2,那么cursor != size返回true。
- next办法中: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和Iterator的关系
- for each以用来解决汇合中的每个元素而不必思考汇合定下标。就是为了让用Iterator简略。然而删除的时候,区别就是在remove,循环中调用汇合remove会导致原汇合变动导致谬误,而应该用迭代器的remove办法。
应用for循环还是迭代器Iterator比照
- 采纳ArrayList对随机拜访比拟快,而for循环中的get()办法,采纳的即是随机拜访的办法,因而在ArrayList里,for循环较快
- 采纳LinkedList则是程序拜访比拟快,iterator中的next()办法,采纳的即是程序拜访的办法,因而在LinkedList里,应用iterator较快
- 从数据结构角度剖析,for循环适宜拜访程序构造,能够依据下标疾速获取指定元素.而Iterator 适宜拜访链式构造,因为迭代器是通过next()和Pre()来定位的.能够拜访没有程序的汇合.
- 而应用 Iterator 的益处在于能够应用雷同形式去遍历汇合中元素,而不必思考汇合类的外部实现(只有它实现了 java.lang.Iterable 接口),如果应用 Iterator 来遍历汇合中元素,一旦不再应用 List 转而应用 Set 来组织数据,那遍历元素的代码不必做任何批改,如果应用 for 来遍历,那所有遍历此汇合的算法都得做相应调整,因为List有序,Set无序,构造不同,他们的拜访算法也不一样.(还是阐明了一点遍历和汇合自身拆散了)。
总结
- 迭代进去的元素都是原来汇合元素的拷贝。
- Java汇合中保留的元素本质是对象的援用,而非对象自身。
- 迭代出的对象也是援用的拷贝,后果还是援用。那么如果汇合中保留的元素是可变类型的,那么能够通过迭代出的元素批改原汇合中的对象。