第十一章 持有对象1
Foreach与迭代器
自学java以来,我用到最方便的遍历方式莫属foreach了,也仅仅会用它,知道它能遍历数组还能便利集合(除Map外)。
foreach能遍历集合是在Java SE5才出现的,Java SE5引入了新的被称为Iterable的接口(java.lang.Iterable),这个接口仅包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此如果你创建了任何实现Iterable的类,都可以将他用于foreach语句中。
既然除Map外的所有集合(Collection对象)都能使用foreach,可想而知,Collection接口肯定是实现了Iterable接口。在Collection或其导出类中肯定对iterator()方法进行了重写,然后产生一个Iterator对象。
源码中其他不相干的我且删掉:
package java.util; import java.util.function.Predicate; import java.util.stream.Stream; import java.util.stream.StreamSupport; public interface Collection<E> extends Iterable<E> {/** * Returns an iterator over the elements in this collection. There are no * guarantees concerning the order in which the elements are returned * (unless this collection is an instance of some class that provides a * guarantee). * * @return an <tt>Iterator</tt> over the elements in this collection */ Iterator<E> iterator(); }
以最常用的ArrayList分析(已删减):
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} 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]; } } }
iterator方法返回的是一个实现了Iterator<E>接口的内部类,没有使用匿名内部类的原因可想而知,Itr内部类肯定需要被再次使用。ArrayList实现了iterator()方法,该方法提供了对ArrayList的迭代。所以foreach对ArrayList的迭代即是间接的调用了iterator方法,实现遍历,从代码中你也可以看到为什么foreach只能从前向后遍历,它只有next()方法。至于Set接口下的导出类看上去很复杂,但是本质上都是一样,在它们内部也必定以某种方式实现了iterator方法。
foreach对于Collection的遍历是对Iterable接口的移动,那foreach对数组的遍历是不是也对Iterable接口的移动?数组作为最基本最常用的存储方式,你找不到任何它是否实现了Iterable接口的说明,但是可以用一个例子测一下:
package mvn.text; import java.util.Arrays; public class TestForeach { public static void main(String[] args) { Integer[] i = {1,2,3,4,5}; //测试ArrayList test(Arrays.asList(i)); //测试数组 //test(i); } public static <T> void test(Iterable<T> itr) { for (T t : itr) { System.out.print(t + " "); } } }
把数组直接代进test函数里发现,编译都通不过。这说明不存在任何从数组到Iterable的自动转换。
那foreach是如何是实现数组的遍历?
猜想:java的设计者们是否是在使用foreach遍历数组时直接将其转换为普通for循环for(int i = 0 ; i < str.length ; i++) { } ?至少看起来这样转换更加合理一些,扩展了对数组的遍历方式,就像使用适配器模式一样,给普通的for循环替换成另一种接口foreach的样式,但根本上还是使用普通for循环而已。