第四十六条 for-each循环优先于传统的for循环

在java 1.5 版本之前,我们对集合的遍历,首选用迭代器
        List c = new ArrayList<>();
        c.add("a");
        for(Iterator i = c.iterator(); i.hasNext(); ){
            String s = (String) i.next();
            System.out.println(s);
        }
遍历数组,用for循环
        for(int i = 0; i< a.length; i++){
            System.out.println(a[i]);
        }
经过上一条,防止 a.length 多次调用,可以优化,改为
        for(int i = 0, count = a.length; i< count; i++){
            System.out.println(a[i]);
        }
上述用for比用while效果要好,但并不是很完美。迭代器和索引会造成某些混乱,或者一不留神,拷贝错误,把一些参数复制错了,就会造成误伤,幸运的是,增强for在java 1.5 时出现了,隐藏了迭代器和索引,适用于 集合 数组 ,上述方法可修改为

        List<String> c = new ArrayList<>();
        for(String s : c){
            System.out.println(s);
        }

        for(int i : a){
            System.out.println(a[i]);
        }

我们发现,for-each也就是增强for,里面没有迭代器与索引,只有对用的对象,这个可以理解是一个封装,对索引和集合的个数一次计算清楚,我们直接调用即可。对于两个集合的遍历,按照数中的例子,把枚举装到集合里,每个集合对应的就是枚举的值

public enum Suit {
    CLUB,DIAMOND,HEART,SPARE
}

public enum Rank {
    ACE,DEUCE,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,TEN,JACK,QUEEN,KING
}

    void test1(){
        Collection<Suit> suit = Arrays.asList(Suit.values());
        Collection<Rank> rank = Arrays.asList(Rank.values());
        for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
            for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
                System.out.println(i.next() + "+" + j.next());
            }
        }
    }
打印结果,估计有点出人意料

CLUB+ACE
DIAMOND+DEUCE
HEART+THREE
SPARE+FOUR
Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)

为什么会这样呢,我们一行行来解读代码,有两个枚举类型,Suit有四种类型,Rank有十三中类型,他们都取了values()值,转换为集合,也就是说集合 suit 元素个数是4个,集合 rank 元素个数是13个,下面是两个for循环嵌套,重点来了,看第一个for循环,集合 suit 获取了迭代器,判断是否有下一个元素,此时假设索引为a,索引a在第一个元素前面,i.hasNext() 判断索引a如果往后移,后面是否有元素,此时,肯定是有的,所以进入里面的for循环,同理,集合rank 也获取了迭代器,假设它的索引为b,j.hasNext() 也判断索引b如果后移,是否还有元素,此时也有,重点来了,看里面的代码 System.out.println(i.next() + "+" + j.next()); 这是个打印日志,但问题是 i.next() 和 j.next(), 也就是说打印元素的同时,索引a 和 索引b 都往后移动了一位,然后继续是里层的for循环,此时 j.hasNext() 仍满足条件,应为 rank 有13个元素,这时候索引b走到第一个元素之后,第二个元素之前,然后再次执行System.out.println(i.next() + "+" + j.next()); 我们发现,打印了两次,i.next() 执行了两次,对应的索引a 往后移动了两位,j.next() 对应的索引b 也往后移动了两位,然后重复上面步骤。也就是说,没打印一次,两个迭代器的索引都往后移动了,由于 外层元素为4个,里层元素13个,里层大于外层,所以在第五次打印时,i.next() 已经数组越界了,因为没有值了,此时外层for循环只执行了一次,内层for循环执行了五次,第五次报错,因为内层for循环判断的条件是 j.hasNext() ,也就是索引b 没有越界,没有判断索引a 的越界,所以报错了。即使不报错,我们是要遍历所有的组合情况,这个逻辑写法也不正确。可以修改为

    void test2(){
        Collection<Suit> suit = Arrays.asList(Suit.values());
        Collection<Rank> rank = Arrays.asList(Rank.values());
        for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
            Suit suitValue = i.next();
            for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
                System.out.println(suitValue + "+" + j.next());
            }
        }
    }
把 i.next(); 也就是索引a 的后移,放在内层for循环之前,这样,每层for循环控制好自己的索引,就可以了。 但是,如果用了增强for,那么就简单多了

    void test3(){
        Collection<Suit> suit = Arrays.asList(Suit.values());
        Collection<Rank> rank = Arrays.asList(Rank.values());
        for (Suit su : suit) {
            for (Rank ra : rank) {
                System.out.println(su + "+" + ra);
            }
        }
    }

简单明了,并且结果正确。


增强for可以遍历数组和集合,也能遍历实现Iterable接口的对象,我们编写类时,实现了这个接口,就可以让调用者使用增强for了,大多数情况,增强for比较好用,有几种情况特殊,无法使用,比如遍历集合,删除指定的元素,增强for循环允许调用一侧remove()方法,但必须马上break或return跳出循环或终止循环,超过一个remove()就会报错,所以删除元素最好还是用传统for循环;遍历数组,并替换某些元素,也需要用传统for;并行遍历多个集合,要控制索引变量,这时候就需要传统for循环了。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值