在 Java 中迭代列表的方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18410035/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:22:23  来源:igfitidea点击:

Ways to iterate over a list in Java

javaloopscollectionsiteration

提问by iX3

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> listobject, I know of the following ways to loop through all elements:

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

Basic forloop(of course, there're equivalent while/ do whileloops 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 Lists, because the actual implementation of the getmethod may not be as efficient as when using an Iterator. For example, LinkedListimplementations must traverse all of the elements preceding i to get the i-th element.

注意:正如@a​​marseillan 所指出的,这种形式对于迭代Lists 来说是一个糟糕的选择,因为该get方法的实际实现可能不如使用Iterator. 例如,LinkedList实现必须遍历 i 之前的所有元素以获取第 i 个元素。

In the above example there's no way for the Listimplementation to "save its place" to make future iterations more efficient. For an ArrayListit doesn't really matter, because the complexity/cost of getis constant time (O(1)) whereas for a LinkedListis it proportional to the size of the list (O(n)).

在上面的示例中,List实现无法“保存其位置”以提高未来迭代的效率。对于 aArrayList它并不重要,因为 的复杂性/成本get是恒定时间(O(1)),而对于 aLinkedList它与列表的大小(O(n))成正比。

For more information about the computational complexity of the built-in Collectionsimplementations, 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

列表迭代器

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.forEach, Stream.forEach, ...

(A map method from Java 8's Stream API (see @i_am_zero's answer).)

(来自 Java 8 的 Stream API 的映射方法(参见 @i_am_zero 的回答)。)

In Java 8 collection classes that implement Iterable(for example, all Lists) now have a forEachmethod, which can be used instead of the for loop statementdemonstrated above. (Here is another questionthat provides a good comparison.)

在 Java 8 中实现的集合类Iterable(例如,所有Lists)现在有一个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).

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

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

采纳答案by Ted Hopp

The three forms of looping are nearly identical. The enhanced forloop:

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

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

is, according to the Java Language Specification, identicalin effect to the explicit use of an iterator with a traditional forloop. 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 removemethod of the iterator itself. 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.

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

In all cases, elementis a reference to the actual list element. None of the iteration methods makes a copy of anything in the list. Changes to the internal state of elementwill always be seen in the internal state of the corresponding element on the list.

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

Essentially, there are 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 both styles, you can come up with essentially trivial variations using for, whileor do whileblocks, but they all boil down to the same thing (or, rather, two things).

本质上,只有两种方法可以迭代列表:使用索引或使用迭代器。增强的 for 循环只是 Java 5 中引入的一种语法快捷方式,以避免显式定义迭代器的乏味。对于这两种风格,您可以使用for,whiledo while块提出本质上微不足道的变化,但它们都归结为同一件事(或者,更确切地说,是两件事)。

EDIT: As @iX3 points out in a comment, you can use a ListIteratorto set the current element of a list as you are iterating. You would need to use List#listIterator()instead of List#iterator()to initialize the loop variable (which, obviously, would have to be declared a ListIteratorrather than an Iterator).

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

回答by shieldgenerator7

You could always switch out the first and third examples with a while loop and a little more code. This gives you the advantage of being able to use the do-while:

你总是可以用一个 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. 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

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

回答by Mario Rossi

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 有点陌生”,这可能很有趣。

回答by iX3

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

回答by eugene82

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));
    }
}

回答by Yu Chen

Right, many alternatives are listed. The easiest and cleanest would be just using the enhanced forstatement as below. The Expressionis of some type that is iterable.

是的,列出了许多替代方案。最简单和最干净的方法是使用如下的增强for语句。在Expression一些类型,它是可迭代的。

for ( FormalParameter : Expression ) Statement

For example, to iterate through, List<String> ids, we can simply so,

例如,要遍历 List<String> ids,我们可以简单地这样,

for (String str : ids) {
    // Do something
}

回答by amarseillan

The basic loop is not recommended as you do not know the implementation of the list.

不建议使用基本循环,因为您不知道列表的实现。

If that was a LinkedList, each call to

如果那是一个 LinkedList,则每次调用

list.get(i)

would be iterating over the list, resulting in N^2 time complexity.

将迭代列表,导致 N^2 时间复杂度。

回答by akhil_mittal

In Java 8we have multiple ways to iterate over collection classes.

Java 8 中,我们有多种方法来迭代集合类。

Using Iterable forEach

使用可迭代的 forEach

The collections that implement Iterable(for example all lists) now have forEachmethod. We can use method-referenceintroduced in Java 8.

实现的集合Iterable(例如所有列表)现在有forEach方法。我们可以使用Java 8 中引入的方法引用

Arrays.asList(1,2,3,4).forEach(System.out::println);

Using Streams forEach and forEachOrdered

使用流 forEach 和 forEachOrdered

We can also iterate over a list using Streamas:

我们还可以使用Stream迭代列表,如下所示:

Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);

We should prefer forEachOrderedover forEachbecause the behaviour of forEachis explicitly nondeterministic where as the forEachOrderedperforms an action for each element of this stream, in the encounter order of the stream if the stream has a defined encounter order. So forEach does not guarantee that the order would be kept.

我们应该优先考虑forEachOrderedforEach因为 的行为forEach是明确不确定的,其中forEachOrdered为该流的每个元素执行操作,如果流具有定义的遇到顺序,则按照流的遇到顺序。所以 forEach 不保证订单会被保留。

The advantage with streams is that we can also make use of parallel streams wherever appropriate. If the objective is only to print the items irrespective of the order then we can use parallel stream as:

流的优点是我们还可以在适当的地方使用并行流。如果目标只是打印项目而不管顺序,那么我们可以使用并行流:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);

回答by CoolMind

For a backward search you should use the following:

对于向后搜索,您应该使用以下内容:

for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
    SomeClass item = iterator.previous();
    ...
    item.remove(); // For instance.
}

If you want to know a position, use iterator.previousIndex(). It also helps to write an inner loop that compares two positions in the list (iterators are not equal).

如果你想知道一个位置,使用 iterator.previousIndex()。它还有助于编写一个内部循环来比较列表中的两个位置(迭代器不相等)。

回答by Sudip Bhandari

You can use forEachstarting from Java 8:

您可以从 Java 8 开始使用forEach

 List<String> nameList   = new ArrayList<>(
            Arrays.asList("USA", "USSR", "UK"));

 nameList.forEach((v) -> System.out.println(v));