java--迭代(二)for,foreach和迭代器详解

这篇文章会详解上篇关于迭代器中出现的问题,当然说是详解,其实我也只能在自己能力内对foreach,迭代器的机制进行了解。其中以arraylist为例子,包含了jdk的源代码。

 首先,for是大家都很熟悉的循环语法,它的基础规则和使用为:

编程中用于循环处理的语句。Java的for语句形式有两种:一种是和C语言中的for语句形式一样,另一种形式用于在集合和数组之中进行迭代。有时候把这种形式称为增强的for(enhanced for)语句,它可以使循环更加紧凑和容易阅读。它的一般形式为: for(;;) 语句; 初始化总是一个赋值语句,它用来给循环控制变量赋初值;条件表达式是一个关系表达式,它决定什么时候退出循环;增量定义循环控制变量每循环一次后按什么方式变化。这三个部分之间用";"分开。

这一段是话百度的解释,但事实上这个地方有一点不太准确的地方,foreach确实只能针对iterator进行迭代,但是与我在开头所提到的迭代器遍历是由一定区别的,我在后面会通过csdn上某位同志遇到的问题进行详细解释。所以在这个时候,我更愿意将for,foreach和迭代器遍历着三种方法分开,而不是将foreach归为迭代器的一种遍历方法。

三种方式的区别:

1)形式区别:

对于for循环,我们采用:

forint i=0;i<arr.size();i++){...}

对于foreach:

forint i:arr){...}

(当然这里的int只是举例子,这里可以是所有实现iterator的接口对象);

在这里多说两句,foreach只能针对实现了iterable接口的对象,其中具体怎么实现的,我有做了部分了解。我去查看了一个foreach的测试程序的字节码。当然这不是今天的重点,我会重新开一个篇章讲述关于java汇编中字节码的事情。如果有兴趣的同学可以点击下面这个网站:

 http://www.cnblogs.com/vinozly/p/5465454.html;

 

对于迭代器的形式:

Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); ...}

2)条件差别

for需要知道数组或者集合的大小,而且需要时有序的,不然无法遍历;

foreach和iterator不需要知道数组或者集合的大小,他们都是得到集合内的每一个元素然后进行处理;

3)多态差别

 for和foreach都需要知道自己的集合类型,甚至要知道自己集合内的元素类型,不能实现多态。这个使用的语法上都可以表示出来。

Iterator是一个接口累心,它不关心集合的累心和集合内的元素类型,因为它是通过hasnext和next来进行下一个元素的判断和获取,这一切都是在集合类型定义的时候就完成的事情。迭代器统一了对容器的访问模式,这也是对接口解耦的最好表现。

4)用法差别

for一般可以用于简单的顺序集合,并且可以预测集合的大小;

foreach可以遍历任何集合或者数组,但是使用者需要知道遍历元素的类型。

iterator是最强大的,它可以随之修改元素内部的元素。可以在遍历的时刻用remove()!!!!我的意思是其他两个都不可以!!!

而且iterator不需要知道元素类型和元素大小,通过hasnext()判断是否遍历完所有元素。

而且在对范型的集合进行遍历的时候,iterator是不二的选择,就是因为不需要知道元素类型便可以遍历。

 

这里讨论完了三个具体的区别,但是我接触到了一个新的东西叫接口解耦。好吧,我会再写一个文章讲一下接口解耦是什么。(感觉坑越填愈多)

 

接下来讲一下iterator实现遍历的具体机制:

我们首先从一个问题程序入手,这个程序如下:

 1 package iteratorTest;
 2 import java.util.*;
 3 
 4 public class TestIterator {
 5     public static void main(String[] args){
 6         List<String> list=new ArrayList<String>();
 7         
 8         list.add("1");
 9         list.add("2");
10         for(String temp : list){
11             if("2".equals(temp))
12             list.remove(temp);
13         }
14         
15         for(String temp : list){
16             System.out.println(temp);
17         }
18         
19         Iterator<String> it=list.iterator();
20         /*while(it.hasNext()){
21             String temp=it.next();
22             if("2".equals(temp))
23                 it.remove();
24         }
25         
26         for(String temp:list){
27             System.out.println(temp);
28         }*/
29     }
30 }

 

我们汇编的时候,程序会出现下面的错误:

当然,我如果更改程序把“1”删除,这个时候就没有问题。我当时也是一脸懵逼呀。

从错误中可以看到,是checForComodification出现了报错。。那我们现在就去看这个地方的源代码:

     final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

这里设计一个java关于多线程的知识,因为iterator是禁止多线程操作的(当然vector的使用会在在后面写。- -!真的是坑越填越多),java用了一个非常简单的机制,就是modcount。记录对集合进行改变的数量,创建一个迭代器与当前集合是要紧耦合的。我不能再我遍历的时候同时去改变这个集合元素,不然会造成混乱。

 

好,这里我们可以确定是modcount不等于expectedmodecount造成的错误,那么及时在foreach内调用next()方法的时候出现了这个问题。我们深入去查看.next()方法。在看之前,我想先把JDK文档中arraylist的方法和数据结构给出来,这在后面理解的时候是非常重要的:

注意我所标记的fastremove方法上面的remove()方法和私有类Itr里面的remove()方法。很明显Itr是一个iterator接口类。注意了!!!但是在foreach的循环语句里面,我们使用的是上面的remove方法,因为我们并没有构造list的迭代器。但是报道的错误信息来源于这个私有类里面的next是foreach擅自为我们进行构造的迭代器,而我们没有使用这个迭代器里面的remove()!!!!!在研究源码的时候我真的是载在这里好久好久。

我们使用的list.remove()是使用的arraylist自身的remove()方法,而不是构造器的方法。在使用的时候我还是清醒的,在调查源代码的过程中整个人都懵逼了。我给出两个remove的具体代码,大家就会知道,arraylist自身方法的remove是不会去调整exceptedmodcount的值,但是却增加了modcount,所以在foreach语句中使用next的checkmodcount的时候发生了错误。

arraylist.remove():

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

 

Itr私有迭代器的remove():

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

 

很明显迭代器中的remove有意识的改变expectedmodcount来使其不报错。而foreach使用的arraylist.remove,改变了modcount,所以才会出现报错现象。

 

然而删除“1”不会报错,是因为删除过后foreach会调用迭代器中的hasnext()进行判断,代码如下:

        public boolean hasNext() {
            return cursor != size;
        }

 

这个时候没有下一个元素,所以不会在arraylist.remove()后调用Itr.next()方法,所以不会进行判断。

这里给出Itr.next()的代码:

        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

可以看到,在Itr.next()的一开始便检查modcount于expectedmodcount是否相同。

 

通过上面的了解,我们使用iterator为什么能够在遍历的时候进行删除操作也可以理解了:

在Itr.remove()方法内有意的将expectedmodcount赋值为modcount,所以不会产生checkformodcount的错误。

 

ps:真的,我在这里研究这么久是因为我看了一个评论,它就是将foreach的机制和arraylist.remove, Itr.remove弄混了,一直在分析迭代器内部是如何影响modcount和expectedcount的。还得到了很多很多赞。我在理解源码的过程中表示,这不是吧expectedmodcount也改变了的么。

 

所以有时候,大家最好能通过源码了解的,都自己去看源码吧,大家对问题有不同的看法,也会有错误,但是源码是不变的。

这篇文章写到这里,很多问题也没有深入,关于foreach的实现和字节码,有时间就会做的。

我还会另开一个数据库的篇章,以怀恋我drop掉的数据库课。不是我不想上,是印度教授真的弄的我头疼。

最后,我想说的就是:

开源大法好!!!!

posted @ 2017-09-26 04:47  霄十一郎  阅读(8414)  评论(0编辑  收藏  举报