本文翻译自:Ways to iterate over a list in Java

Being somewhat new to the Java language I'm trying to familiarize myself with all the ways (or at least the non-pathological ones) that one might iterate through a list (or perhaps other collections) and the advantages or disadvantages of each. 对Java语言有些陌生,我试图使自己熟悉所有可能遍历列表(或其他集合)的方式(或至少是非病理性方式)以及每种方式的优缺点。

Given a List<E> list object, I know of the following ways to loop through all elements: 给定一个List<E> list对象,我知道以下遍历所有元素的方式:

Basic for loop (of course, there're equivalent while / do while loops as well) 基本的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

    // ...
}

Note: As @amarseillan pointed out, this form is a poor choice for iterating over List s, because the actual implementation of the get method may not be as efficient as when using an Iterator . 注意:正如@amarseillan指出的那样,这种形式对于迭代List是一个糟糕的选择,因为get方法的实际实现可能不如使用Iterator时高效。 For example, LinkedList implementations must traverse all of the elements preceding i to get the i-th element. 例如, LinkedList实现必须遍历i之前的所有元素才能获得第i个元素。

In the above example there's no way for the List implementation to "save its place" to make future iterations more efficient. 在上面的示例中, List实现无法“保存位置”以使将来的迭代更加有效。 For an ArrayList it doesn't really matter, because the complexity/cost of get is constant time (O(1)) whereas for a LinkedList is it proportional to the size of the list (O(n)). 对于一个ArrayList它其实并不重要,因为复杂性/成本get是恒定的时间(O(1)),而对于LinkedList是成正比的列表的大小(为O(n))。

For more information about the computational complexity of the built-in Collections implementations, check out this question . 有关内置Collections实现的计算复杂度的更多信息,请查看此问题

Enhanced for loop (nicely explained in this question ) 增强了for循环此问题对此做了很好的解释)

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

    // ...
}

Iterator 迭代器

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

    // ...
}

Functional Java 功能性Java

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

Iterable.forEach , Stream.forEach , ... Iterable.forEachStream.forEach ,...

(A map method from Java 8's Stream API (see @i_am_zero's answer).) (来自Java 8的Stream API的map方法(请参阅@i_am_zero的答案)。)

In Java 8 collection classes that implement Iterable (for example, all List s) now have a forEach method, which can be used instead of the for loop statement demonstrated above. 在实现Iterable Java 8集合类(例如,所有List )中,现在具有forEach方法,可以使用该方法代替上面演示的for循环语句 (Here is another question that provides a good comparison.) (这是另一个可以很好比较的问题。)

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

What other ways are there, if any? 还有什么其他方式(如果有)?

(BTW, my interest does not stem at all from a desire to optimize performance ; I just want to know what forms are available to me as a developer.) (顺便说一句,我的兴趣根本不是出于优化性能的愿望;我只是想知道开发人员可以使用哪些形式。)


#1楼

参考:https://stackoom.com/question/1FFI3/遍历Java中的列表的方法


#2楼

You could always switch out the first and third examples with a while loop and a little more code. 您总是可以使用while循环和更多的代码切换出第一个和第三个示例。 This gives you the advantage of being able to use the do-while: 这为您提供了可以使用do-while的优势:

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

Of course, this kind of thing might cause a NullPointerException if the list.size() returns 0, becuase it always gets executed at least once. 当然,如果list.size()返回0,则这种情况可能会导致NullPointerException,因为它总是至少执行一次。 This can be fixed by testing if element is null before using its attributes / methods tho. 这可以通过在使用元素的属性/方法之前测试元素是否为空来解决。 Still, it's a lot simpler and easier to use the for loop 尽管如此,使用for循环要简单得多


#3楼

I don't know what you consider pathological, but let me provide some alternatives you could have not seen before: 我不知道您认为病理是什么,但让我提供一些您之前从未见过的选择:

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

Or its recursive version: 或其递归版本:

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

Also, a recursive version of the classical for(int i=0... : 另外,经典的for(int i=0...的递归版本:

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

I mention them because you are "somewhat new to Java" and this could be interesting. 我之所以提到它们,是因为您“对Java有点陌生”,这可能很有趣。


#4楼

Example of each kind listed in the question: 问题中列出的每种示例:

ListIterationExample.java 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());
     }
}

#5楼

The three forms of looping are nearly identical. 三种形式的循环几乎相同。 The enhanced for loop: 增强的for循环:

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

is, according to the Java Language Specification , identical in effect to the explicit use of an iterator with a traditional for loop. 根据Java语言规范 ,其作用与显式使用带有传统for循环的迭代器相同 In the third case, you can only modify the list contents by removing the current element, and then only if you do it through the remove method of the iterator itself. 在第三种情况下,您只能通过删除当前元素来修改列表内容,然后再通过迭代器本身的remove方法来进行操作。 With index-based iteration, you are free to modify the list in any way. 使用基于索引的迭代,您可以自由地以任何方式修改列表。 However, adding or removing elements that come before the current index risks having your loop skipping elements or processing the same element multiple times; 但是,添加或删除当前索引之前的元素可能会导致循环跳过元素或多次处理同一元素; you need to adjust the loop index properly when you make such changes. 进行此类更改时,您需要适当地调整循环索引。

In all cases, element is a reference to the actual list element. 在所有情况下, element都是对实际list元素的引用。 None of the iteration methods makes a copy of anything in the list. 没有一种迭代方法可以复制列表中的任何内容。 Changes to the internal state of element will always be seen in the internal state of the corresponding element on the list. element内部状态的更改将始终在列表中相应元素的内部状态中显示。

Essentially, there only two ways to iterate over a list: by using an index or by using an iterator. 本质上,只有两种方法可以遍历列表:使用索引或使用迭代器。 The enhanced for loop is just a syntactic shortcut introduced in Java 5 to avoid the tedium of explicitly defining an iterator. 增强的for循环只是Java 5中引入的语法快捷方式,以避免显式定义迭代器的繁琐工作。 For both styles, you can come up with essentially trivial variations using for , while or do while blocks, but they all boil down to the same thing (or, rather, two things). 对于这两种样式,您都可以使用forwhiledo while块提供一些琐碎的变体,但是它们全都归结为同一件事(或者说是两件事)。

EDIT: As @iX3 points out in a comment, you can use a ListIterator to set the current element of a list as you are iterating. 编辑:作为@ IX3在评论中指出,你可以使用ListIterator设置列表的当前元素为你迭代。 You would need to use List#listIterator() instead of List#iterator() to initialize the loop variable (which, obviously, would have to be declared a ListIterator rather than an Iterator ). 您将需要使用List#listIterator()而不是List#iterator()来初始化循环变量(显然,必须将其声明为ListIterator而不是Iterator )。


#6楼

A JDK8-style iteration: JDK8样式的迭代:

public class IterationDemo {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("element " + elem));
    }
}