有位小朋友最近正在为年后换工作做准备,但是遇到一个问题,觉得很不可思议的一道笔试题。然后我把这道题发到技术群里,发现很多人居然不知道,很多都是连蒙带猜的说。感觉很有必要写一篇文章来说道说道。
奇怪的笔试题
阅读下面这段代码,请写出这段代码的输出内容:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
public class Test {
public static void main(String[] args) {
List list &#61; new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator iterator &#61; list.iterator();
while (iterator.hasNext()) {
String str &#61; (String) iterator.next();
if (str.equals("2")) {
iterator.remove();
}
}
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("4");
}
}
他写出来的答案是&#xff1a;
1
3
4
奇怪的是&#xff0c;你把这道题目发给你身边人&#xff0c;让他们回答这道面试题输出结果是什么&#xff0c;说这个结果的人非常多。不行你试试
~
答案明显不对&#xff0c;因为在第一个while里的 iterator.hasNext()false后才会到第二个while里来&#xff0c;同一个Iterator对象&#xff0c;前面调一次iterator.hasNext()false&#xff0c;再判断一次结果不还是一样吗&#xff1f;&#xff0c;
所以第二个while判断为false&#xff0c;也就不会再去遍历iterator了&#xff0c;由此可知本体答案是&#xff1a;4。
下面我们来分析一下为什么是具体底层是怎么实现的。
这里的Iterator是什么&#xff1f;
迭代器是一种模式、详细可见其设计模式&#xff0c;可以使得序列类型的数据结构的遍历行为与被遍历的对象分离&#xff0c;即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部
Iterable 实现这个接口的集合对象支持迭代&#xff0c;是可以迭代的。实现了这个可以配合foreach使用~
Iterator 迭代器&#xff0c;提供迭代机制的对象&#xff0c;具体如何迭代是这个Iterator接口规范的。
Iterator说明
public interface Iterator {
//每次next之前&#xff0c;先调用此方法探测是否迭代到终点
boolean hasNext();
//返回当前迭代元素 &#xff0c;同时&#xff0c;迭代游标后移
E next();
/*删除最近一次已近迭代出出去的那个元素。
只有当next执行完后&#xff0c;才能调用remove函数。
比如你要删除第一个元素&#xff0c;不能直接调用 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 {
int cursor; // index of next element to return
int lastRet &#61; -1; // index of last element returned; -1 if no such
//modCountshi ArrayList中的属性&#xff0c;当添加或删除的时候moCount值会增加或者减少
//这里主要是给fail-fast使用&#xff0c;避免一遍在遍历&#xff0c;一遍正在修改导致数据出错
//此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改列表&#xff0c;
//或者以这样的方式对其进行扰动,进步可能会产生错误的结果。
int expectedModCount &#61; modCount;
public boolean hasNext() {
//cursor初始值为0&#xff0c;没掉一次next方法就&#43;1
//size是ArrayList的大小
return cursor !&#61; size;
}
&#64;SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i &#61; cursor;
if (i >&#61; size)
throw new NoSuchElementException();
//把ArrayList中的数组赋给elementData
Object[] elementData &#61; ArrayList.this.elementData;
if (i >&#61; elementData.length)
throw new ConcurrentModificationException();
//每调用一次next方法&#xff0c;游标就加1
//cursor&#61;lastRet&#43;1
cursor &#61; i &#43; 1;
//返回ArrayList中的元素
return (E) elementData[lastRet &#61; i];
}
public void remove() {
if (lastRet <0)
throw new IllegalStateException();
checkForComodification();
try {
//调用ArrayList中remove方法&#xff0c;溢出该元素
ArrayList.this.remove(lastRet);
//cursor&#61;lastRet&#43;1&#xff0c;
//所以此时相当于cursor&#61;cursor-1
cursor &#61; lastRet;
lastRet &#61; -1;
expectedModCount &#61; modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount !&#61; expectedModCount)
throw new ConcurrentModificationException();
}
}
再回到上面题目中&#xff1a;
第一个iterator.hasNext()
第1次循环
hasNext方法中&#xff1a;cursor0&#xff0c; size3&#xff0c;所以cursor !&#61; size返回true。
next方法中&#xff1a;cursor&#61;0&#43;1。返回"1"。
第2次循环
hasNext方法中&#xff1a;cursor1&#xff0c; size3&#xff0c;所以cursor !&#61; size返回true。
next方法中&#xff1a;cursor&#61;1&#43;1。返回"2"。
remove方法中&#xff1a;cursorcursor-12-1&#61;1&#xff0c;把ArrayList中的"2"给删除了&#xff0c;所以size&#61;&#61;2。
第3次循环
hasNext方法中&#xff1a;cursor1&#xff0c; size2&#xff0c;那么cursor !&#61; size返回true。
next方法中&#xff1a;cursor&#61;1&#43;1&#61;&#61;2&#xff1b;返回"3"。
第4次循环
hasNext方法中&#xff1a;cursor2&#xff0c; size2&#xff0c;那么cursor !&#61; size返回false。
第二个iterator.hasNext()
hasNext方法中&#xff1a;cursor2&#xff0c; size2&#xff0c;所以cursor !&#61; size返回false。
所以&#xff0c;最后只输出"4"&#xff0c;即答案为4.
Iterator与泛型搭配
Iterator对集合类中的任何一个实现类&#xff0c;都可以返回这样一个Iterator对象。可以适用于任何一个类。
因为集合类(List和Set等)可以装入的对象的类型是不确定的,从集合中取出时都是Object类型,用时都需要进行强制转化,这样会很麻烦,用上泛型,就是提前告诉集合确定要装入集合的类型,这样就可以直接使用而不用显示类型转换.非常方便.
foreach和Iterator的关系
for each以用来处理集合中的每个元素而不用考虑集合定下标。就是为了让用Iterator简单。但是删除的时候&#xff0c;区别就是在remove&#xff0c;循环中调用集合remove会导致原集合变化导致错误&#xff0c;而应该用迭代器的remove方法。
使用for循环还是迭代器Iterator对比
采用ArrayList对随机访问比较快&#xff0c;而for循环中的get()方法&#xff0c;采用的即是随机访问的方法&#xff0c;因此在ArrayList里&#xff0c;for循环较快
采用LinkedList则是顺序访问比较快&#xff0c;iterator中的next()方法&#xff0c;采用的即是顺序访问的方法&#xff0c;因此在LinkedList里&#xff0c;使用iterator较快
从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合.
而使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素&#xff0c;而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口)&#xff0c;如果使用 Iterator 来遍历集合中元素&#xff0c;一旦不再使用 List 转而使用 Set 来组织数据&#xff0c;那遍历元素的代码不用做任何修改&#xff0c;如果使用 for 来遍历&#xff0c;那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.(还是说明了一点遍历和集合本身分离了)。
总结
迭代出来的元素都是原来集合元素的拷贝。
Java集合中保存的元素实质是对象的引用&#xff0c;而非对象本身。
迭代出的对象也是引用的拷贝&#xff0c;结果还是引用。那么如果集合中保存的元素是可变类型的&#xff0c;那么可以通过迭代出的元素修改原集合中的对象。