Java 集合(四)| 各种常用集合类型的遍历输出 | Iterator接口讲解

一、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后的新增的遍历循环方式就以后更新吧…


– – 与其穷尽一生等一个完美的别人,不如花时间来好好修炼不完美的自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值