每个循环,还是一个迭代器?

哪种是遍历集合的最有效的方法?

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a) {
  integer.toString();
}

要么

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
   Integer integer = (Integer) iterator.next();
   integer.toString();
}

请注意,尽管最后一个问题的答案之一接近了,但这不是这个,这个,这个或这个的完全重复。 这不是一个愚蠢的原因是,其中大多数比较循环,你在循环内部调用get(i) ,而不是使用迭代器。

正如Meta上的建议,我将发布我对这个问题的回答。


如果您只是在集合上漫游以读取所有值,则使用迭代器或新的for循环语法没有区别,因为新语法只是在水下使用迭代器。

但是,如果你想循环使用旧的“c-style”循环:

for(int i=0; i<list.size(); i++) {
   Object o = list.get(i);
}

然后,根据底层的数据结构,新的for循环或迭代器可以更加高效。 原因是对于某些数据结构, get(i)是O(n)操作,它使循环成为O(n2)操作。 传统的链表是这种数据结构的一个例子。 所有迭代器都有一个基本要求, next()应该是O(1)操作,从而生成循环O(n)。

要验证迭代器在新的for循环语法中是否在水下使用,请比较以下两个Java片段中生成的字节码。 首先是for循环:

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a)
{
  integer.toString();
}
// Byte code
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 3
 GOTO L2
L3
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 2 
 ALOAD 2
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L2
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L3

其次,迭代器:

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
  Integer integer = (Integer) iterator.next();
  integer.toString();
}
// Bytecode:
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 2
 GOTO L7
L8
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 3
 ALOAD 3
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L7
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L8

正如您所看到的,生成的字节码实际上是相同的,所以使用任何一种形式都没有性能损失。 因此,您应该选择最符合美学要求的循环形式,因为大多数人将是for-each循环,因为它具有较少的样板代码。


差异不在于性能,而在于能力。 当直接使用引用时,显式使用迭代器类型(例如List.iterator()和List.listIterator(),但在大多数情况下它们返回相同的实现)有更多的权力。 你也有能力在循环中引用迭代器。 这允许您执行诸如从集合中删除项目而不会收到ConcurrentModificationException的情况。

例如

还行吧:

Set<Object> set = new HashSet<Object>();
// add some items to the set

Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
     Object o = setIterator.next();
     if(o meets some condition){
          setIterator.remove();
     }
}

这不是,因为它会抛出一个并发修改异常:

Set<Object> set = new HashSet<Object>();
// add some items to the set

for(Object o : set){
     if(o meets some condition){
          set.remove(o);
     }
}

为了扩展Paul自己的答案,他已经证明,该特定编译器(大概是Sun的javac?)上的字节码是相同的,但不同的编译器不能保证生成相同的字节码,对吗? 要查看两者之间的实际区别,让我们直接找到源代码并检查Java Language Specification,特别是14.14.2,“增强的for语句”:

增强for说法是相当于基本for形式的语句:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiers(opt) Type Identifier = #i.next();    
    Statement 
}

换句话说,JLS要求两者相同。 理论上这可能意味着字节码的边际差异,但实际上增强的for循环需要:

  • 调用.iterator()方法
  • 使用.hasNext()
  • 通过.next()使局部变量可用
  • 换句话说,为了所有实际目的,字节码将是相同的或几乎相同的。 很难想象任何编译器的实现都会导致两者之间的显着差异。

    链接地址: http://www.djcxy.com/p/18029.html

    上一篇: each loop, or an iterator?

    下一篇: Java 8 Iterable.forEach() vs foreach loop