657

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

    // ...
}

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

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

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

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

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

4

13 回答 13

308

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

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

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

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

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

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

于 2013-08-23T19:29:06.723 回答
51

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

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());
     }
}
于 2013-08-23T19:22:20.903 回答
26

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

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

list.get(i)

将遍历列表,导致 N^2 时间复杂度。

于 2015-04-08T19:22:19.063 回答
21

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));
    }
}
于 2014-04-06T17:01:47.460 回答
8

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

使用可迭代的 forEach

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

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

使用流 forEach 和 forEachOrdered

我们还可以使用Stream遍历列表:

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 不保证订单会被保留。

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

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
于 2016-05-24T09:38:54.493 回答
6

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

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

或者它的递归版本:

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

此外,经典的递归版本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);
}

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

于 2013-08-23T19:18:52.060 回答
2

您可以从 Java 8 开始使用forEach :

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

 nameList.forEach((v) -> System.out.println(v));
于 2018-01-22T10:51:17.077 回答
1

java 8您可以使用List.forEach()方法 withlambda expression来迭代列表。

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);
                }
        );
    }
}
于 2018-09-06T13:34:26.467 回答
0

对,列出了许多替代方案。最简单和最干净的方法就是使用for下面的增强语句。Expression是某种可迭代的类型。

for ( FormalParameter : Expression ) Statement

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

for (String str : ids) {
    // Do something
}
于 2014-08-25T21:14:30.537 回答
0

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

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

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

于 2016-11-25T13:12:18.380 回答
0

在 Java 8 或更高版本中,您可以使用 forEach() 方法迭代 Hashset。

import java.util.HashSet;

public class HashSetTest {
    public static void main(String[] args) {
        
         HashSet<String> hSet = new HashSet<String>();
          
         // Adding elements into your HashSet usind add()
         hSet.add("test1");
         hSet.add("test2");
         hSet.add("test3");
      
         // Iterating over hash set items
         hSet.forEach(x -> System.out.println(x));
         // Or you can write shorter:
         hSet.forEach(System.out::println);
    }
}
于 2021-11-18T13:29:22.737 回答
-1

在上面,您会发现遍历 LIST 的所有不同方法。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class test1 {

public static void main(String[] args) {
    //******* Exercise 1 : Write a Java program to create a new array list, add some colors (string) and print out the collection.
    List<String> colors = new ArrayList<String>();
    colors.add("Black");
    colors.add("Red");
    colors.add("Green");
    colors.add("Blue");
    System.out.println(colors);
    
    
    //******* Exercise 2 : Write a Java program to iterate through all elements in a array list. 
    System.out.println("//******* Exercise 2");
    List<Integer> list2 = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    
    // iteration type 1 : using FOR loop
    System.out.println("// iteration type 1");
    for(Integer nb : list2) {
        System.out.print(nb + ", ");
    }
    System.out.println("\n");
    
    // iteration type 2 : using FOR loop
    System.out.println("// iteration type 2");
    for(int i=0; i < list2.size(); i++) {
        System.out.print(list2.get(i) + ", ");
    }System.out.println("\n");
    
    // iteration type 3  : using Do-While loop
    System.out.println("// iteration type 3");
    int index21 = 0;
    
    do {
        System.out.print(list2.get(index21) + ", ");
        index21++;
    }while(index21<list2.size());
    System.out.println("\n");
    
    
    // iteration type 4  : using While loop
    System.out.println("// iteration type 4");
    int index22 = 0;
    while(index22<list2.size()) {
        System.out.print(list2.get(index22) + ", ");
        index22++;
    }

    System.out.println("\n");
    
    
    // iteration type 5  : using  Iterable forEach loop 
    System.out.println("// iteration type 5");
     list2.forEach(elt -> {
         System.out.print(elt + ", ");
     });

    System.out.println("\n");
    
    
    // iteration type 6  : using  Iterator
    System.out.println("// iteration type 6");
    Iterator<Integer> listIterator = list2.iterator();
    while(listIterator.hasNext()) {
        System.out.print( listIterator.next() + ", ");
    }
    
    System.out.println("\n");
    
    // iteration type 7  : using  Iterator (From the beginning)
    System.out.println("// iteration type 7");
    ListIterator<Integer> listIterator21 = list2.listIterator(list2.size());
    while(listIterator21.hasPrevious()) {
        System.out.print( listIterator21.previous() + ", ");
    }

    System.out.println("\n");   
    
    // iteration type 8  : using  Iterator (From the End)
    System.out.println("// iteration type 8");
    ListIterator<Integer> listIterator22 = list2.listIterator();
    while(listIterator22.hasNext()) {
        System.out.print( listIterator22.next() + ", ");
    }

    System.out.println("\n");   
}

}
于 2020-09-26T16:54:59.967 回答
-3

您总是可以使用 while 循环和更多代码来切换第一个和第三个示例。这为您提供了能够使用 do-while 的优势:

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

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

于 2013-08-23T19:07:36.090 回答