关于循环:在Java中迭代列表的方法

Ways to iterate over a list in Java

对于Java语言有点陌生,我试图让自己熟悉所有的方法(或者至少是非病理性的),可以通过列表(或者其他集合)和每个方法的优缺点来迭代。

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

基本for循环(当然,也有等效的while/do while循环)

1
2
3
4
5
6
7
8
// 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所指出的,这种形式是一种糟糕的选择。因为实际实现了get方法可能不如使用Iterator时有效。例如,LinkedList实现必须遍历前面的元素得到第i个元素。

在上面的示例中,List的实现无法"保存它的位置"以使未来的迭代更有效。对于一个ArrayList来说,它实际上并不重要,因为get的复杂性/成本是恒定的时间(o(1)),而对于一个LinkedList来说,它与列表的大小(o(n))成比例。

有关内置Collections实现的计算复杂性的更多信息,请查看此问题。

增强的for循环(在这个问题中解释得很好)

1
2
3
4
5
for (E element : list) {
    // 1 - can call methods of element

    // ...
}

迭代器

1
2
3
4
5
6
7
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

    // ...
}

记录器

1
2
3
4
5
6
7
8
9
10
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

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

iterable.foreach,stream.foreach,…

(来自Java 8的流API的映射方法(参见@ IyAM0Z0的答案))

在Java 8中,实现EDCOX1、12、(例如,所有EDCOX1,3,s)的集合类现在有一个EDCOX1×14的方法,它可以用来代替上面演示的for循环语句。(这是另一个提供了很好比较的问题。)

1
2
3
4
5
6
7
8
9
10
11
12
13
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).

如果有的话,还有什么别的办法?

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


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

1
2
3
for (E element : list) {
    . . .
}

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

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

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

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


问题中列出的每种示例:

ListIterationExample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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());
     }
}


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

如果是LinkedList,则每次调用

1
list.get(i)

将遍历列表,导致n^2时间复杂性。


JDK8样式迭代:

1
2
3
4
5
6
7
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));
    }
}


在Java 8中,我们有多种方式对集合类进行迭代。

使用Iterable ForEach

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

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

使用流foreach和foreachored

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

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

我们应该更喜欢forEachOrdered而不是forEach,因为forEach的行为是明确的不确定性的,当forEachOrdered为该流的每个元素执行一个动作时,如果该流有一个定义的遭遇顺序,则按照该流的遭遇顺序执行。所以foreach不能保证订单能被保存。

流的优势在于,我们还可以在适当的情况下使用并行流。如果目标只是打印项目,不管顺序如何,那么我们可以使用并行流作为:

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

我不知道你认为什么是病理性的,但让我提供一些你以前没见过的替代品:

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

或其递归版本:

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

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

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

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


可以从Java 8开始使用Frace:

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

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

java 8中,可以使用List.forEach()方法和lambda expression迭代列表。< BR>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;

public class TestA {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}


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

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

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


对,列出了许多备选方案。最简单、最干净的方法就是使用下面的增强型for语句。Expression是某种不可测的类型。

1
for ( FormalParameter : Expression ) Statement

例如,要遍历listids,我们可以简单地这样做,

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


您可以使用while循环和更多的代码来切换第一个和第三个示例。这使您能够在以下情况下使用Do:

1
2
3
4
5
6
int i = 0;
do{
 E element = list.get(i);
 i++;
}
while (i < list.size());

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