共计 4065 个字符,预计需要花费 11 分钟才能阅读完成。
有位小朋友最近正在为年后换工作做筹备,然而遇到一个问题,感觉很不堪设想的一道口试题。而后我把这道题发到技术群里,发现很多人竟然不晓得,很多都是连蒙带猜的说。感觉很有必要写一篇文章来说道说道。
奇怪的口试题
浏览上面这段代码,请写出这段代码的输入内容:
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");
}
}
他写进去的答案是:
1
3
4
奇怪的是,你把这道题目发给你身边人,让他们答复这道面试题输入后果是什么,说这个后果的人十分多。不行你试试
~
答案显著不对,因为在第一个 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 汇合中保留的元素本质是对象的援用,而非对象自身。
- 迭代出的对象也是援用的拷贝,后果还是援用。那么如果汇合中保留的元素是可变类型的,那么能够通过迭代出的元素批改原汇合中的对象。
正文完