在 Java 中迭代列表的方法

新手上路,请多包涵

作为 Java 语言的新手,我试图让自己熟悉可能遍历列表(或其他集合)的所有方式(或至少是非病态的方式)以及每种方式的优缺点。

给定一个 List<E> list 对象,我知道以下遍历所有元素的方法:

基本 for 循环(当然,也有等效的 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 是一个糟糕的选择,因为 get 方法的实际实现可能不如使用 Iterator 。例如, LinkedList 实现必须遍历 i 之前的所有元素以获取第 i 个元素。

在上面的示例中, List 实现无法“保存其位置”以提高未来迭代的效率。对于 ArrayList 这并不重要,因为 get 的复杂性/成本是常数时间 (O(1)) 而对于 LinkedList 与列表的大小成正比 (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

    // ...
}

列表迭代器

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 的回答)。)

在实现 Iterable 的Java 8集合类中(例如,所有 List s)现在有一个 forEach 方法,而不是 循环语句 上面证明了。 (这是 另一个提供很好比较的问题。)

 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).

如果有的话,还有哪些其他方法?

(顺便说一句,我的兴趣根本不是出于 优化性能 的愿望;我只是想知道作为开发人员可以使用哪些形式。)

原文由 jacobq 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 590
2 个回答

这三种循环形式几乎相同。增强的 for 循环:

 for (E element : list) {
    . . .
}

根据 Java Language Specification ,在效果上 等同 于在传统 for 循环中显式使用迭代器。在第三种情况下,您只能通过删除当前元素来修改列表内容,然后,只有通过迭代器本身的 remove 方法才能完成。使用基于索引的迭代,您可以自由地以任何方式修改列表。但是,添加或删除当前索引之前的元素可能会导致循环跳过元素或多次处理同一元素;进行此类更改时,您需要适当调整循环索引。

在所有情况下, element 是对实际列表元素的引用。没有一种迭代方法会复制列表中的任何内容。对 element 的内部状态的更改将始终在列表中相应元素的内部状态中看到。

本质上,只有两种方法可以迭代列表:使用索引或使用迭代器。增强的 for 循环只是 Java 5 中引入的一种语法快捷方式,用于避免显式定义迭代器的单调乏味。对于这两种样式,您可以使用 forwhiledo while 来想出本质上微不足道的变化—相反,两件事)。

编辑:正如@iX3 在评论中指出的那样,您可以在迭代时使用 ListIterator 设置列表的当前元素。您需要使用 List#listIterator() 而不是 List#iterator() 来初始化循环变量(显然,必须将其声明为 ListIterator 而不是 Iterator )。

原文由 Ted Hopp 发布,翻译遵循 CC BY-SA 4.0 许可协议

问题中列出的每种类型的示例:

ListIterationExample.java

 import java.util.*;

public class ListIterationExample {

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

        // 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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());
     }
}

原文由 jacobq 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题