首页 文章

在Java中迭代列表的方法

提问于
浏览
482

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

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

Basic 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

    // ...
}

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

在上面的示例中, List 实现"save its place"无法使未来的迭代更有效 . 对于 ArrayList 并不重要,因为 get 的复杂性/成本是恒定时间(O(1))而对于 LinkedList 它是否与列表的大小成比例(O(n)) .

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

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

    // ...
}

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.forEach,Stream.forEach,...

(来自Java 8的Stream API的map方法(参见@ i_am_zero的回答) . )

在Java 8中,实现 Iterable 的集合类(例如,所有 List s)现在都有 forEach 方法,可以使用它来代替上面演示的for loop statement . (这是another question提供了一个很好的比较 . )

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

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

(顺便说一句,我的兴趣并不是出于对optimize performance的渴望;我只是想知道作为开发人员我可以使用哪些形式 . )

11 回答

  • 40

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

    for ( FormalParameter : Expression ) Statement
    

    例如,要遍历List <String> id,我们可以这样做,

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

    java 8 中,您可以使用 List.forEach() 方法和 lambda 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);
                    }
            );
        }
    }
    
  • 17

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

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

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

  • -2

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

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

  • 4

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

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

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

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

  • 0

    问题中列出的每种示例:

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

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

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

    list.get(i)
    

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

  • 225

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

  • 2

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

    您可以从Java 8开始使用forEach

    List<String> nameList   = new ArrayList<>(
                Arrays.asList("USA", "USSR", "UK"));
    
     nameList.forEach((v) -> System.out.println(v));
    

相关问题