黑马程序员——Foreach的原理

来源:互联网 发布:ubuntu安装 分区 编辑:程序博客网 时间:2024/06/07 12:16

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

 

 我们在使用for循环时,经常为for循环的结束标志而苦恼,尤其是做嵌套for循环对多维数组进行遍历时,更是令人头痛,稍微一疏忽就会发生indexOutOfBoundsException(数组角标越界)错误,为此在Java SE 5.0中推出了一种for循环的高级应用模式for each,它可以让我们很方便的依次处理数组中的每一个元素(其他类型的元素集合亦可) 而不必为指定下标值而分心。

Foreach语句的格式为

       for(variable : collection) statement

定义一个变量用于暂存集合中的每一个元素,并执行相应的语句,collection这一集合表达式必须是一个数组或者是一个实现了Iterable接口的类对象,例如:
       int[]  a={………};

       For(int temp a[]){

              Syso(temp);

}

遍历数组a中的元素并打印出来

Foreach语句的特点

       foreachfor循环的简写模式,那么它把for循环简写之后的优点和缺点是什么呢?

优点:可以让我们很方便的遍历一个实现Iterable接口类的对象(集合、数组或其它的什么),尤其是foreach在遍历多维的集合或数组时更是叫人称赞不已,它省去让我们苦恼的下标值(不必为下标的起始值和终止值操心),可以让我们专心的来完成其他工作。
       int[][] a=new int[i][j];

假定a已经赋值,如果我们要遍历a的话,我们要在内循环调用外循环的循环变量,来设定内循环的终止值,代码如下:

       for(int x=0;x

              for(y=0;y

}

}

类似这样设定的嵌套循环非常的容易出现indexOutOfBoundsException(数组角标越界)异常,但是我们如果使用foreach就可以很方便的使用嵌套循环来遍历多维数组或集合,例:
       for(int[] temp : a){

              for(int a : temp){

                     syso(a);

}

}

由此可见foreach的好处显而易见,它使循环语句显得更加简洁、更不易出错。

缺点:事物往往是相对,有其优点就有其缺点foreach语句也是这样的,foreach是一种功能很强的循环结构,但是其始终替代不了for循环,是因为要使用foreach循环必须要有一个被遍历项,所以在数组和集合之外foreach就显得无用武之地了。此外我们在用foreach遍历数组或集合过程中,我们不可对数组或集合中的元素进行操作,至于其原理我们稍后解释。

Foreach的原理      

要使用foreach来遍历的对象必须实现Iterable接口,这是因为foreach内部其实是封装了一个迭代器对象来对所要遍历的对象进行迭代,而这个迭代器的来源是通过所要遍历对象的Iterator  iterator()方法来 获取的,Iterator  iterator()这个方法封装在Iterable接口中,所以要遍历对象必须实现Iterable 接口。此过程用代码表示为:

Iterable接口的代码:

public interface Iterable {

    Iteratoriterator();

}

Foreach内部实现代码(此处为自己写的foreach内部实现原理代码并不一定准确)

       调用for(Type type : object ) statement 就相当于

              Iterator  it=object.iterator();

              while(it.hasNext())

              {

                     type=it.next();

                     statement

}

迭代器

       我们知道Foreach内部,封装了一个迭代器,那么迭代器又是什么呢?

       将一个对象中的数据依次取出是一个复杂的动作,它包括判断动作,和取出动作,我们用一个方法不足以描述这个动作,所以就要用内部类来描述它,而每一个要被迭代的类都封装了这样一个内部类,那么我们就可以将这个内部类的共性特点抽取出来,由此我们就得到了一个Iterator,这个接口就是迭代器接口。这个接口的内部代码为:

public interface Iterator {

    boolean hasNext();

    E next();

    void remove();

}

由此我们可知迭代器共有三个方法:

1、判断下一个元素是否存在

2、取出下一个元素

3、删除元素

我们可以使用前两个方法来对元素进行迭代,使用第三个方法删除当前元素。

       迭代器的由来和迭代器所包含的方法我们已经知道,那么在类的内部是如何实现迭代器的呢?在ArrayList以内部类实现迭代器 代码如下:

  private class Itr implements Iterator {

        int cursor;      

        int lastRet = -1;

        int expectedModCount = modCount;  

        public boolean hasNext() {

            return cursor != size;

        }

        @SuppressWarnings("unchecked")

        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];

        }

 

        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();

            }

        }

 

        final void checkForComodification() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

        }

    }

这段代码中有许多莫名的属性,我们依此来说明:

Cursor

用来指定当前元素的下一个元素

lastRet

用来指定当前元素

modCount

在父类AbstractList中定义的一个int型属性:modcount,用来记录ArrayList结构性变化的次数。在ArrayList中所涉及结构变化的方法有:add()remove()addAll()removeRange()clear()方法。这些方法每调用一次,modCount的值就加1

Size

       可以理解为当前对象存储数据的长度

 @SuppressWarnings("unchecked")

这是Java SE 5.0的新特性注释类型指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

checkForComodification

       检测机制,假如在对集合迭代期间集合的结构性发生了改变,即在对集合遍历的同时还对集合进行的增删操作,那么将导致expectedModCount modCount不相等,checkForComodification可以检测出来并抛出ConcurrentModificationException异常。

elementData

       这个可以理解为一个带角标的数据缓冲区,将集合中的元素取出放入此缓冲区中,然后依次遍历此缓冲区中的元素来实现遍历集合的目的。集合对象的容量就是此数据缓冲区的长度,该长度至少要足以包含集合对象的所有元素。

       在迭代器的三个方法中还有一些,检测异常的代码,我在这里就不说了。

ForeachIteratorIterable的关系

       我们在foreach内部封装了迭代器,我们只要通过对象获取迭代器对象就行了,即对象所属的类的内部类只有实现Iterator接口就行了,和Iterable有什么关系?

       Foreach要通过一个固定的方法获取对象的迭代器对象,而只要类的内部具有实现Iterator接口的内部类,那么此类就要有这个方法,因此我们将这个方法单独抽取出来封装为Iterable接口,这就是Iterable接口的由来。

       要使用Foreach方法,则所要遍历对象所属的类必须实现Iterable接口,而实现Iterable接口,则此类内部又必须能产生Iterator对象(即:必须内部类实现Iterator接口)。

 

Foreach的使用注意事项

       foreach的原理我们已经知道,那么我们不难得知使用foreach应注意的事项:

1、不可在使用foreach迭代集合或数组的同时对它们进行增删操作。

2、在没有特殊情况下foreach内部最好使用一个  object.next(); 如若不然将非常容易出现角标越界异常。

原创粉丝点击