在Java中迭代列表的方法

iX3*_*iX3 543 java iteration collections loops

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

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

基本 循环(当然,还有就是相当于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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

在上面的例子中,List实现没有办法"保存它的位置"以使未来的迭代更有效.因为ArrayList它并不重要,因为复杂性/成本get是恒定时间(O(1)),而a LinkedList是它与列表的大小(O(n))成比例.

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

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

for (E element : list) {
    // 1 - can call methods of element

    // ...
}
Run Code Online (Sandbox Code Playgroud)

迭代器

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

的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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

功能Java

list.stream().map(e -> e + 1); // Can apply a transformation function for e
Run Code Online (Sandbox Code Playgroud)

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).
Run Code Online (Sandbox Code Playgroud)

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

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

Ted*_*opp 250

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

for (E element : list) {
    . . .
}
Run Code Online (Sandbox Code Playgroud)

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

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

实质上,只有两种方法可以迭代列表:使用索引或使用迭代器.增强的for循环只是Java 5中引入的一个语法快捷方式,以避免显式定义迭代器的繁琐.对于这两种款式,你可以拿出本质琐碎的变化使用for,whiledo while块,但他们都归结为同样的事情(或者说,两件事情).

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


iX3*_*iX3 43

问题中列出的每种示例:

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());
     }
}
Run Code Online (Sandbox Code Playgroud)


ama*_*lan 20

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

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

list.get(i)
Run Code Online (Sandbox Code Playgroud)

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

  • 我在那篇文章中读到的正是我所说的......你在读什么部分? (5认同)

eug*_*e82 19

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));
    }
}
Run Code Online (Sandbox Code Playgroud)


i_a*_*ero 6

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

使用Iterable forEach

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

Arrays.asList(1,2,3,4).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

使用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);
Run Code Online (Sandbox Code Playgroud)

我们应该更喜欢forEachOrderedforEach,因为行为forEach是明确不确定性,其中作为forEachOrdered执行此流的每一个元素的动作,如果流具有确定的遭遇订单流的遭遇秩序.因此,forEach不保证订单会被保留.

流的优点是我们也可以在适当的地方使用并行流.如果目标只是打印项目而不管订单如何,那么我们可以使用并行流作为:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)


Mar*_*ssi 5

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

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

或者它的递归版本:

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

此外,经典的递归版本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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 好的。我不喜欢被称为“病态”,所以这里有一个解释:我在跟踪或跟随树中的路径时使用过它们。多一个?在将一些 Lisp 程序翻译成 Java 时,我不想让他们失去 Lisp 精神,我也做了同样的事情。如果您认为这些是有效的、非病理性的用途,请点赞评论。我需要一个拥抱!!!:-) (3认同)