在Java中迭代列表的方法



Answers

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

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

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

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

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




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



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

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

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




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



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

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

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




Related