在Java中迭代列表的方法

时间:2013-08-23 19:00:15

标签: java loops collections iteration

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

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

基本for loop(当然,还有等效的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指出的那样,这种形式是一个糟糕的选择 用于迭代List s,因为实际执行 get方法可能不如使用Iterator时有效。 例如,LinkedList实现必须遍历所有 i之前的元素得到第i个元素。

在上面的示例中,List实现无法实现 “保存它的位置”,使未来的迭代更有效率。 对于ArrayList来说,它并不重要,因为get的复杂性/成本是恒定时间(O(1)),而LinkedList的复杂性/成本是与Collections的大小成正比的清单(O(n))。

有关内置for (E element : list) { // 1 - can call methods of element // ... } 实施的计算复杂性的更多信息,请查看this question

增强型for loop(很好地解释为in this question

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

    // ...
}

Iterator

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

    // ...
}

ListIterator

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

Functional Java

Iterable

Iterable.forEachStream.forEach,...

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

在实现List的Java 8集合类中(例如,所有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). 方法,可以使用它来代替for loop statement演示以上。 (这里another question提供了一个很好的比较。)

{{1}}

有什么其他方式,如果有的话?

(顺便说一下,我的兴趣并不完全取决于对optimize performance的渴望;我只是想知道作为开发者我可以使用哪些形式。)

12 个答案:

答案 0 :(得分:238)

三种形式的循环几乎完全相同。增强的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)。

答案 1 :(得分:40)

问题中列出的各种示例:

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

答案 2 :(得分:20)

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

如果那是LinkedList,则每次调用

list.get(i)

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

答案 3 :(得分:18)

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

答案 4 :(得分:6)

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

使用Iterable forEach

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

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

使用Streams 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);

答案 5 :(得分:4)

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

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

答案 6 :(得分:2)

您可以从Java 8开始使用forEach

Mail::send('emails.mailExample', $user, function($message) use ($user) {
    $message->to($user->email);
    $message->subject('E-Mail Example');
});

答案 7 :(得分:0)

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

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

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

答案 8 :(得分:0)

java 8中,您可以将List.forEach()的{​​{1}}方法与列表一起使用。

lambda expression

答案 9 :(得分:0)

上面,您将找到各种遍历LIST的不同方式。

if __name__ == "__main__": main()

答案 10 :(得分:-1)

是的,列出了许多替代品。最简单,最干净的只是使用增强的for语句,如下所示。 Expression属于可迭代的某种类型。

for ( FormalParameter : Expression ) Statement

例如,要迭代,List&lt; String&gt; ids,我们可以这么简单,

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

答案 11 :(得分:-2)

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

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

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