在Java中对列表进行迭代的方法

作为一个刚接触Java语言的人,我想让自己熟悉所有可能在列表(或其他集合)中进行迭代的方法(至少是非病态的),以及每种方法的优点或缺点。

给定一个Listlist对象,我知道有以下几种方法可以循环浏览所有元素。

Basic for loop (当然,也有相等的while/do while循环)

// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
    E element = list.get(i);
    // 1 - can call methods of element
    // 2 - can use 'i' to make index-based calls to methods of list

    // ...
}

注意:正如@amarseillan所指出的,这种形式对于迭代 "List "来说是个糟糕的选择。 遍历 "List "的一个糟糕选择,因为 "get "方法的实际实现 因为 "get "方法的实际实现可能没有使用 "Iterator "时那么有效。 例如,LinkedList的实现必须遍历i前面的所有元素,以获得 前面的元素来获取第i个元素。

在上面的例子中,"List "的实现没有办法做到 保存它的位置"以使未来的迭代更有效率。 对于 "数组列表 "来说,这并不重要,因为 "获取 "的复杂性/成本是恒定的(O(1)),而对于 "链接列表 "来说,它与列表的大小成正比(O(n))。

关于内置Collections实现的计算复杂性的更多信息,请查看这个问题

增强的for循环(很好地解释了本问题)。

for (E element : list) {
    // 1 - can call methods of element

    // ...
}

迭代器

for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list

    // ...
}

ListIterator

for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
    E element = iter.next();
    // 1 - can call methods of element
    // 2 - can use iter.remove() to remove the current element from the list
    // 3 - can use iter.add(...) to insert a new element into the list
    //     between element and iter->next()
    // 4 - can use iter.set(...) to replace the current element

    // ...
}

功能Java

list.stream().map(e -> e + 1); // Can apply a transformation function for e

Iterable.forEach, Stream.forEach, ...

(Java 8'的Stream API中的一个映射方法(见@i_am_zero'的回答)。)

在Java 8中,实现了Iterable的集合类(例如,所有的List)现在都有一个forEach方法,可以用它来代替上面演示的for循环语句。(这里有另一个问题,提供了一个很好的比较)。)

Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
//     (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
//     being performed with each item.

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).

如果有的话,还有什么方法?

(BTW,我的兴趣完全不是源于对[优化性能][迭代器性能问题]的渴望;我只是想知道作为一个开发者,有哪些形式可以利用。)

问题中列出的每一种的例子。

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List numbers = new ArrayList();

        // populates list with initial values
        for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7))
            numbers.add(i);
        printList(numbers);         // 0,1,2,3,4,5,6,7

        // replaces each element with twice its value
        for (int index=0; index < numbers.size(); index++) {
            numbers.set(index, numbers.get(index)*2); 
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // does nothing because list is not being changed
        for (Integer number : numbers) {
            number++; // number = new Integer(number+1);
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14  

        // same as above -- just different syntax
        for (Iterator iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            number++;
        }
        printList(numbers);         // 0,2,4,6,8,10,12,14

        // ListIterator<?> provides an "add" method to insert elements
        // between the current element and the cursor
        for (ListIterator iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.add(number+1);     // insert a number right before this
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        // Iterator<?> provides a "remove" method to delete elements
        // between the current element and the cursor
        for (Iterator iter = numbers.iterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            if (number % 2 == 0)    // if number is even 
                iter.remove();      // remove it from the collection
        }
        printList(numbers);         // 1,3,5,7,9,11,13,15

        // ListIterator<?> provides a "set" method to replace elements
        for (ListIterator iter = numbers.listIterator(); iter.hasNext(); ) {
            Integer number = iter.next();
            iter.set(number/2);     // divide each element by 2
        }
        printList(numbers);         // 0,1,2,3,4,5,6,7
     }

     public static void printList(List numbers) {
        StringBuilder sb = new StringBuilder();
        for (Integer number : numbers) {
            sb.append(number);
            sb.append(",");
        }
        sb.deleteCharAt(sb.length()-1); // remove trailing comma
        System.out.println(sb.toString());
     }
}
评论(0)

我不知道你认为什么是病态的,但让我提供一些你以前可能没有见过的替代品。

List sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

或其递归版本。

void visit(List list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

另外,经典的for(int i=0...的递归版本。

void visit(List list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

我提到它们是因为你是"对Java有点陌生",这可能很有趣。

评论(5)

你总是可以用一个while循环和更多一点的代码来换掉第一和第三个例子。这样你就有了能够使用do-while的优势。

int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

当然,如果list.size()返回0,这种事情可能会导致NullPointerException,因为它总是至少被执行一次。这可以通过在使用属性/方法之前测试元素是否为空来解决。不过,使用for循环要简单得多,也更容易。

评论(3)