loops - 在Java中迭代列表的方法




collections iteration (9)

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

对于Java语言而言,我尝试熟悉所有可能通过列表(或者其他集合)迭代的方式(或者至少是非病态方法)以及每种方式的优缺点。

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

基本for loop (当然, do 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 ,它与列表的大小成正比(O(n))。

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

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

    // ...
}

功能性Java

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

Iterable.forEachStream.forEach ,...

(来自Java 8的Stream API的映射方法(请参阅@ i_am_zero的答案))

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

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

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


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

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

您可以从Java 8开始使用forEach

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

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

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

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


你可以随时用第一个和第三个例子用一个while循环和一些更多的代码。 这为您提供了使用do-while的优势:

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

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


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为流的每个元素执行一个操作,如果流具有定义的遭遇顺序,则按照流的遇到顺序执行操作。 所以每个人都不能保证订单会被保留。

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

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

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

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

list.get(i)

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


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

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

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

在所有情况下, element都是对实际列表元素的引用。 这些迭代方法都不会在列表中生成任何副本。 element内部状态的变化将始终在列表中相应元素的内部状态中看到。

本质上,只有两种方法可以遍历列表:使用索引或使用迭代器。 增强型for循环只是Java 5中引入的一种语法快捷方式,可以避免显式定义迭代器的麻烦。 对于这两种风格,你可以想出使用forwhile或者block的基本微不足道的变体,但它们都归结为相同的东西(或者更确切地说,是两件事)。

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


Algorithm           ArrayList   LinkedList
seek front            O(1)         O(1)
seek back             O(1)         O(1)
seek to index         O(1)         O(N)
insert at front       O(N)         O(1)
insert at back        O(1)         O(1)
insert after an item  O(N)         O(1)

算法:Big-Oh表示法

ArrayLists适合一次写入多次读取或appender,但在前面或中间添加/删除时效果不佳。





java loops collections iteration