Java 集合源码解析(1):Iterator
Java, Android 开发也有段时间了,当初为了早点学 Android,Java 匆匆了解个大概就结束了,基础不够扎实。
虽然集合框架经常用,但是一直没有仔细看看原理,仅止于会用,不知道为什么要这么做。
这段时间就开始 Java 集合的源码学习。
点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~
Java 提供的 集合类都在 Java.utils 包下,其中包含了很多 List, Set, Map, Queue… 它们的关系如下面这张类图所示:
可以看到,Java 集合主要分为两类:Collection 和 Map. 而 Collection 又继承了 Iterable< E > 接口,Iterable 接口内只有一个 iterator 方法,返回一个 Iterator 迭代器:
public interface Iterable<T> {
/**
* Returns an {@link Iterator} for the elements in this object.
*
* @return An {@code Iterator} instance.
*/
Iterator<T> iterator();
}
本篇文章将介绍 Iterator 迭代器。 在介绍 Iterator 之前不得不提一下被它替代的 Enumeration< E >:
Enumeration< E >
public interface Enumeration<E> {
/**
* Returns whether this {@code Enumeration} has more elements.
*
* @return {@code true} if there are more elements, {@code false} otherwise.
* @see #nextElement
*/
public boolean hasMoreElements();
/**
* Returns the next element in this {@code Enumeration}.
*
* @return the next element..
* @throws NoSuchElementException
* if there are no more elements.
* @see #hasMoreElements
*/
public E nextElement();
}
Enumeration 是一个很古老的迭代器,有两个方法:
- hasMoreElements() //是否还有元素
- nextElement() //返回下一个元素
Enumeration 的实现类会生成一系列子元素,比如 StringTokenizer;通过 Enumeration 的上述两个方法可以用来遍历它实现类的元素,比如这样:
//StringTokenizer : 切割, Breaks a string into tokens; new code should probably use {@link String#split}.
Enumeration enumeration = new StringTokenizer("A-B-C", "-");
while (enumeration.hasMoreElements()){
System.out.println(enumeration.nextElement());
}
运行结果:
Enumeration 接口早在 JDK 1.0 时就推出了,当时比较早的容器比如 Hashtable, Vector 都使用它作为遍历工具。
那 Enumeration 为什么会被废弃呢?
根据官方文档:
NOTE: The functionality of this interface is duplicated by the Iterator interface. In addition, Iterator adds an optional remove operation, and has shorter method names. New implementations should consider using Iterator in preference to Enumeration.
可以大胆猜一下,应该是当初设计没有考虑全,只有两个方法,而且名字还太长了 - -。 后来在 JDK 1.2 推出了 Iterator 替代它的功能。虽然 Enumeration 在 JDK 1.5 后增加了泛型的应用,依旧大势已去。
Iterator
Iterator 是一个集合上的迭代器,用来替代 Enumeration 进行遍历、迭代。
它 和 Enumeration 有什么不同呢?
根据官方文档:
Iterators differ from enumerations in two ways:
- Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
- Method names have been improved.
哈哈首先是名字缩短了,看来大家都懒得输入那么长的方法名。 其次是 允许调用者在遍历过程中语法正确地删除元素。
注意这个 [语法正确],事实上我们在使用 Iterator 对容器进行迭代时如果修改容器 可能会报 ConcurrentModificationException 的错。官方称这种情况下的迭代器是 fail-fast 迭代器。
fail-fast 与 ConcurrentModificationException
以 ArrayList 为例,在调用迭代器的 next,remove 方法时:
public E next() {
if (expectedModCount == modCount) {
try {
E result = get(pos + 1);
lastPosition = ++pos;
return result;
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
throw new ConcurrentModificationException();
}
public void remove() {
if (this.lastPosition == -1) {
throw new IllegalStateException();
}
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
try {
AbstractList.this.remove(lastPosition);
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
expectedModCount = modCount;
if (pos == lastPosition) {
pos--;
}
lastPosition = -1;
}
可以看到在调用迭代器的 next,remove 方法时都会比较 expectedModCount 和 modCount 是否相等,如果不相等就会抛出 ConcurrentModificationException ,也就是成为了 fail-fast。
而 modCount 在 add, clear, remove 时都会被修改:
public boolean add(E object) {
//...
modCount++;
return true;
}
public void clear() {
if (size != 0) {
//...
modCount++;
}
}
public boolean remove(Object object) {
Object[] a = array;
int s = size;
if (object != null) {
for (int i = 0; i < s; i++) {
if (object.equals(a[i])) {
//...
modCount++;
return true;
}
}
} else {
for (int i = 0; i < s; i++) {
if (a[i] == null) {
//...
modCount++;
return true;
}
}
}
return false;
}
因此我们知道了 fail-fast 即 ConcurrentModificationException 出现的原因,怎么解决呢?
方法一:
用 CopyOnWriteArrayList,ConcurrentHashMap 替换 ArrayList, HashMap,它们的功能和名字一样,在写入时会创建一个 copy,然后在这个 copy 版本上进行修改操作,这样就不会影响原来的迭代。不过坏处就是浪费内存。
方法二:
使用 Collections.synchronizedList 加 同步锁,不过这样有点粗暴。
可能得方法三(待考究,目前我还没搞清楚):
在学习 ListView 中的观察者模式 时,我注意到 DataSetObservable 的 notifyChanged 方法中有如下注释:
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
to avoid such problems, just march thru the list in the reverse order
为了避免影响 ArrayList 迭代,倒序处理。 待考究,目前我还没搞清楚。
不过意外的发现了,原来 for-each 的循环内部也是使用了 Iterator 来遍历Collection,它也调用了 Iterator.next(),所以在修改元素时会检查(元素的)变化并抛出 ConcurrentModificationException。
在从任何 Collection中删除对象时总要使用 Iterator 的remove 方法, for-each 循环只是标准 Iterator 代码标准用法之上的一种语法糖(syntactic sugar)而已。
差点忘了 Iterator 的使用
所有 Collection 的子类都有 iterator() 方法来获得 Iterator,通过 Iterator 的标准操作方法,可以让我们不必关心具体集合的类型,从而避免向客户端暴露出集合的内部结构。
不使用 Iterator 遍历集合是这样的:
for(int i=0; i<集合的大小;i++){
// ...
}
使用 Iterator 遍历集合是这样的:
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
对比而言,后者客户端代码与具体集合类型耦合性弱,复用性更强。缺点就是无法获取指定的元素,只能挨个遍历。
Thanks
https://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html
https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html
http://www.cnblogs.com/dolphin0520/p/3933551.html
http://blog.csdn.net/chenssy/article/details/38151189
http://blog.csdn.net/mazhimazh/article/details/17730517
http://javarevisited.blogspot.jp/2014/04/for-each-loop-puzzle-in-java-example.html
Java 集合源码解析(1):Iterator的更多相关文章
- java集合 源码解析 学习手册
学习路线: http://www.cnblogs.com/skywang12345/ 总结 1 总体框架 2 Collection架构 3 ArrayList详细介绍(源码解析)和使用示例 4 fai ...
- Java 集合源码解析(2):ListIterator
点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~ 今天心情和股票一样红,还是学学 ListIterator 吧! ListIterator 根据官方文档介绍, ListIt ...
- java集合源码分析几篇文章
java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915
- Java集合源码分析(三)LinkedList
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- Java集合源码学习(一)集合框架概览
>>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...
- Java集合类源码解析:Vector
[学习笔记]转载 Java集合类源码解析:Vector 引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...
- Java——LinkedHashMap源码解析
以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述 哈希表和链表基于Map接口的实现,其具有可预测的迭 ...
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
随机推荐
- 超实用的JavaScript代码段 Item4 --发送短信验证码
发送短信验证码 实现点击“发送验证码”按钮后,按钮依次显示为“59秒后重试”.“58秒后重试”…直至倒计时至0秒时再恢复显示为“发送验证码”.在倒计时期间按钮为禁用状态 . 第一步.获取按钮.绑定事件 ...
- 集成 Apple Pay
作者感言 在中秋过后终于把国内的三大支付平台SDK集成都搞定了, 现在我们终于可以来研究Apple自家的支付Apple Pay最后:如果你有更好的建议或者对这篇文章有不满的地方, 请联系我, 我会参考 ...
- 关闭“JDK自动更新”提示
- 转--CSS选择器详解(一)常用选择器
今天复习一下CSS的知识,看了篇文章觉得很好,转来备用. 转自:http://www.cnblogs.com/fattydoit/p/3492028.html 目录 类型选择器 类选择器 ID选择器 ...
- firefox hack
@-moz-document url-prefix(){ css选择器 { css样式设置 } }
- Android selector选择器的使用
通常按钮在点击前和后有两种状态,比如点击前为蓝色,点击后为灰色,且不再响应点击事件. 如果不使用selector选择器,点击后,就需要在程序中进行以下的类似操作 button1.setBackgrou ...
- String.equals()方法的实现代码,
通常对String的比较有两种情况,一个是使用==,另一个是使用equals()方法,注意==是对对象的地址进行比较的,而String中的equals()方法是覆盖了Object类的方法,并且实现为对 ...
- 编写自己的Windows Live Writer插件
起因 自从小猪使用Windows Live Writer(wlw)来写博客之后就很少打开网站的后台编辑器了,这真是个写博客的好东西啊,但是任何东西都是不完美的.索契冬奥会开幕式都会把五环弄成四环呢!对 ...
- InLineHookSSDT
//当Ring3调用OpenProcess //1从自己的模块(.exe)的导入表中取值 //2Ntdll.dll模块的导出表中执行ZwOpenProcess(取索引 进入Ring0层) //3进入R ...
- 神奇的Noip模拟试题第一试 合理种植 枚举+技巧
1.合理种植 (plant.pas/.c/.cpp) [问题描述] 大COS在氯铯石料场干了半年,受尽了劳苦,终于决定辞职.他来到表弟小cos的寒树中学,找到方克顺校长,希望寻个活干. 于是他如愿以偿 ...