文章目录
一、Iterator接口
在搞懂集合的遍历输出之前,先清楚下Iterator接口是有必要的
1.接口历史
从Collection接口的继承关系中可以看出,Collection接口继承了Iterable接口,这个接口是JDK1.5引入的,在Iterable接口中有个iterator()
方法,它返回的是一个Iterator接口对象;实际上在JDK1.5之前,iterator()
方法直接声明在Collection接口之中,只不过在引入了Iterable接口之后,便把这个方法迁移到了Iterable接口之中了
至于为什么引入Iterable接口,我现在知道的是实现了Iterable接口的集合可以实现增强for循环,在JDK1.8中接口里面仅有三个方法,其中的iterator()
方法上面已经提过,还有个forEach()
方法(和我们的增强for循环:for-each不同),它是新增的遍历集合的方法,在JDK1.8后可配合lambda
表达式通过函数式编程来进行遍历循环,但实际中我们还是用的传统的遍历方法居多,所以本文中不作讲述
2.Iterator接口作用
Iterator接口的定义:
public interface Iterator<E> {
boolean hasNext();
E next();
//下面defalut修饰符,java8新增,用于修饰在接口中已经被具体实现方法
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
从Iterator接口的定义的我们可以知道Iterator接口的作用:通过Collection集合中的iterator()
方法返回一个Iterator接口对象,每次对next()
的调用都给出集合的下一项,hasNext()
用来告诉是否存在下一项,由于Iterator接口中的方法有限,因此,很难用Iterator作简单遍历Collection集合以外的事情,说白了就是用来遍历集合,但是我们应该注意,一次hasNext()方法后,只能使用一次next(),不然可能会出现一系列的错误,具体后面的遍历会讲到
但是这里不得不提一下Iterator接口的remove()
方法
3.Iteartor接口的remove()方法
了解Collection接口的就知道,Collection接口也包含一个remove()
方法,它们两者的使用场景是不同的,但是使用Iterator的remove
方法可能会有更多的优点
- Collection接口中的
remove()
方法必须首先找出要被删除的项,如果知道所要删除的项的准确位置那么删除它的开销可能要小很多 - Iteartor的
remove()
方法只能用在集合使用iterator迭代时用于删除刚才迭代过的值,而如果在迭代过程中,使用Collection接口的remove()方法,可能会抛出一个异常:ConcurrentModificationException
(并发修改异常)
抛出异常的原因,是因为当直接使用Iterator迭代集合时,有一个基本原则
:
如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法,在其后再次使用该迭代器时可能会抛出上述异常
我们会在各种的集合实现类的源码中发现一个int型变量:modCount,该变量记录了集合被修改的次数,该变量可以被用来判断是否要抛出上述异常,关于modCount详细介绍参考:通过ArrayList对modCount的操作分析fail-fast 机制
至于为什么有这样一个原则,是因为避免以下情况出现:
- 当迭代器准备给出某一项作为下一项,但该项却被Collection接口的
remove
方法删除 - 或者有一个新的项通过Collection接口的
add
方法插入到正在遍历项的前面
只要上述情况出现,我们就不难知道迭代过程将会出现错误了,所以,我们只有在需要立即使用一个迭代器迭代的时候(而不是迭代过程还希望增删),才应该获取该迭代器。但是我们前面已经提到了,该迭代器调用它自己的remove
方法是合法的,因为它只能删除刚才已经迭代过的对象
4.总结
可以看出,Iterator接口就是为了遍历Collection集合而设计的(尽管遍历集合的方法不止一种)
但是我们还应该想到一个问题,就是像HashMap这样的Map结构没有实现Itearable
接口,内部也没有直接实现iterator
方法,所以它提供了像entrySet
(entrySet利用了EntrySet内部类,该类实现了iterator
方法)这样的方法来通过遍历Set集合(实现了Collection接口)来遍历HashMap(当然也不止这一种方法),这里不再详细赘述关于HashMap内部结构,建议读者自行查看分析,下面我们直接举例来进行常用集合的遍历
二、集合的遍历输出
1.List的遍历
(1)ArrayList的遍历
public class Test {
public static void main(String[] args) {
List<String> msg = new ArrayList<>();
msg.add("暑假快要来了");
msg.add("开学还会远吗");
msg.add("在家快发霉了");
msg.add("学校还不管吗");
//1.for循环遍历
for(int i=0;i<msg.size();i++) {
System.out.println(msg.get(i));
}
//2.增强for循环遍历
for (String aMsg : msg) {
System.out.println(aMsg);
}
//3.带for的iterator遍历
for(Iterator<String> iter=msg.iterator();iter.hasNext();) {
System.out.println(iter.next());
}
//4.带while的iterator遍历
Iterator<String> iter=msg.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
}
}
2.Set的遍历
(1)HashSet的遍历
HashSet的遍历和ArrayList的遍历很相似,但是HashSet中元素是无序不重复的,所以没有提供get()方法
public class Test {
public static void main(String[] args) {
Set<String> msg = new HashSet<>();
msg.add("暑假快要来了");
msg.add("开学还会远吗");
msg.add("在家快发霉了");
msg.add("学校还不管吗");
//1.增强for循环遍历
for (String aMsg : msg) {
System.out.println(aMsg);
}
//2.带for的iterator遍历
for(Iterator<String> iter=msg.iterator();iter.hasNext();) {
System.out.println(iter.next());
}
//3.带while的iterator遍历
Iterator<String> iter=msg.iterator();
while(iter.hasNext()) {
System.out.println(iter.next());
}
}
}
3.Map的遍历
(1)HashMap的遍历
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("status", "ERROR");
map.put("errcode", "40010");
map.put("errmsg", "系统错误");
System.out.println(map);
//1.先获得key的Set集合,再根据遍历Set集合的三种方式遍历key获取value,这里只举例一种
//通过增强for循环遍历
Set<String> keySet = map.keySet();
for (String aKeySet : keySet) {
System.out.println(aKeySet+": "+map.get(aKeySet));
}
//2.直接获取key-value的Set集合,再通过遍历Set集合的三种方法遍历key-value,这里只举例两种
Set<Map.Entry<String,String>> entrySet = map.entrySet();
//2.1通过for循环遍历
for(Iterator<Map.Entry<String,String>> iter=entrySet.iterator();iter.hasNext();){
Map.Entry<String,String> temp = iter.next();
System.out.println(temp.getKey()+": "+temp.getValue());
}
//2.2通过增强for循环遍历
for (Map.Entry<String, String> temp : entrySet) {
System.out.println(temp.getKey() + ": " + temp.getValue());
}
}
}
4.遍历陷阱
在我们使用Iterator迭代器进行迭代的时候,我们应该注意下面几点:
- 迭代中一次hasNext()后只能使用一次next(),否则可能会出现不可预料的错误
- 迭代中不能调用集合中add()或者remove()等修改集合结构的方法,只能使用Iterator内部的remove()方法,否则可能会抛出
ConcurrentModificationException
(并发修改异常)
示例一:一次hasNext()后使用了多次next()方法
这个陷阱可能会很隐蔽:
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
map.put("four", "4");
map.put("five", "5");
Set<String> keySet = map.keySet();
// 当我们想在一行中就输出map的键和值时
for(Iterator<String> iter=keySet.iterator();iter.hasNext();){
System.out.println(iter.next()+": "+map.get(iter.next()));
}
}
}
/**output
four: 1
two: 3
Exception in thread "main" java.util.NoSuchElementException
*/
可以看到我们的简写就可能会不经意间发生错误,正确的做法是用增强for循环或者声明中间变量来存储next()得到的值,下面的解决方法用的是后者:
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
map.put("four", "4");
map.put("five", "5");
Set<String> keySet = map.keySet();
for(Iterator<String> iter=keySet.iterator();iter.hasNext();){
String key = iter.next();//中间变量来存储
System.out.println(key+": "+map.get(key));
}
}
}/**output
four: 4
one: 1
two: 2
three: 3
five: 5
*/
示例二:Iterator迭代过程中使用了集合的remove()方法
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
map.put("four", "4");
map.put("five", "5");
Set<String> keySet = map.keySet();
for(Iterator<String> iter=keySet.iterator();iter.hasNext();){
String key = iter.next();//中间变量来存储
System.out.println(key+": "+map.get(key));
map.remove(key);
}
}
}/**output
four: 4
Exception in thread "main" java.util.ConcurrentModificationException
*/
可以看到,当执行到remove()方法后直接抛出异常;正确的做法是尽量不要在迭代过程增删元素,但是我们可以使用Iteartor内部的remove()方法来删除,但该方法不提供参数,所以只能做到删除刚才遍历过的元素
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
map.put("four", "4");
map.put("five", "5");
Set<String> keySet = map.keySet();
for(Iterator<String> iter=keySet.iterator();iter.hasNext();){
String key = iter.next();
System.out.println(key+": "+map.get(key));
iter.remove();//调用iter的remove()方法
}
//再次输出
System.out.println("删除后的map:"+map);
}
}/**output
four: 4
one: 1
two: 2
three: 3
five: 5
删除后的map:{}
*/
上述示例只是在HashMap的基础上作错误示范,但我们应该知道,在ArrayList、HashSet等集合在使用Iteartor遍历过程中出现上述错误做法也会发生异常
4.总结
通过对常用集合的遍历可以看出,Iterator迭代器遍历是集合通用的遍历方式,但要注意正确的使用方法避免出现异常,而增强for循环可以对遍历过程大大简化,在没有引入JDK1.8新特性的lambda表达式或者函数式编程方法来通过forEach()遍历的时候,使用增强for循环无疑是最简单的,而关于JDK1.8后的新增的遍历循环方式就以后更新吧…
– – 与其穷尽一生等一个完美的别人,不如花时间来好好修炼不完美的自己。