Способы итерации по списку в Java

Будучи новичком в языке Java, я пытаюсь ознакомиться со всеми способами (или, по крайней мере, непатологическими), которыми можно итерировать список (или, возможно, другие коллекции), и преимуществами или недостатками каждого из них.

Имея объект List list, я знаю следующие способы перебора всех элементов:

Basic 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

    // ...
}

Примечание: Как отметил @amarseillan, эта форма является плохим выбором для итерации по спискам, потому что фактическая реализация метода 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

    // ...
}

итератор

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

    // ...
}

Functional Java

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

Iterable.forEach, Stream.forEach, ...

(Метод map из Java 8's Stream API (см. ответ @i_am_zero's)).

В Java 8 классы коллекций, реализующие Iterable (например, все 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).

Какие еще существуют способы, если таковые имеются?

(BTW, мой интерес вовсе не проистекает из желания оптимизировать производительность; я просто хочу знать, какие формы доступны мне как разработчику).

Комментарии к вопросу (11)
Решение

Трех форм цикла почти идентичны. Расширенный цикл for:

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

это, согласно спецификацией языка Java, identical в силу явного использования итератора с традиционной для петли. В третьем случае, вы можете только изменять содержимое списка путем удаления текущего элемента, а затем только если вы делаете это через "удалить" способ самого итератора. С индексными итерации, вы можете изменить список в любом случае. Однако, добавляя или удаляя элементы, которые приходят до текущих рисков индекса, ваш цикл пропуск элементов или обработки один и тот же элемент несколько раз, нужно правильно отрегулировать цикла, когда вы делаете такие изменения.

Во всех случаях, "элемент" является ссылкой на фактический элемент списка. Ни один из методов итерации делает копию чего-либо в списке. Изменения внутреннего состояния "элемент" всегда будет видно внутреннего состояния соответствующего элемента в списке.

По сути, есть только два способа перебора списка: с помощью индекса или с помощью итератора. Улучшенный цикл for-это просто синтаксический ярлык введены в Java 5, чтобы избежать скуки явного задания итератора. Для обоих стилей, можно придумать, казалось бы, тривиальное вариации с помощью к, А или не время, но все они сводятся к тому же (или, вернее, две вещи).

Редактировать: как @iX3 аппликации, отмечает в комментарии, Вы можете использовать ListIterator для текущего элемента списка, как вы итерации. Вы должны использовать список#listIterator() вместо список#итератор() для инициализации переменной цикла (который, очевидно, должен быть объявлен ListIterator, а не итератор).

Комментарии (7)

Пример каждого вида, перечисленного в вопросе:

ListIterationExample.java

import java.util.*;

public class ListIterationExample {

     public static void main(String []args){
        List numbers = new ArrayList();

        // 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 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 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 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 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 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 сложность.

Комментарии (3)

В JDK8 стиле итерации:

public class IterationDemo {

    public static void main(String[] args) {
        List list = Arrays.asList(1, 2, 3);
        list.stream().forEach(elem -> System.out.println("element " + elem));
    }
}
Комментарии (3)

В в Java 8 мы имеем несколько способов, чтобы выполнить итерации по коллекции классов.

Используя Итерируемому объекту

Коллекции, которые реализуют итератор (например, все списки) теперь есть метод объекту. Мы можем использовать метод Ссылка введены в Java 8.

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

Используя потоки foreach и forEachOrdered

Мы также можем перебрать список, используя Трансляция Как:

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

Мы должны предпочесть forEachOrdered за объекту, потому что поведение объекту явно недетерминированные где как forEachOrdered` выполняет действие для каждого элемента этого потока, в поединке того потока, если поток обладает определенными столкнуться с тем. Так что foreach не гарантируем, что заказ будет храниться.

Преимущество потоков заключается в том, что мы также можем использовать параллельные потоки по мере необходимости. Если цель состоит только, чтобы печатать предметы независимо от того, то можно использовать параллельный поток, как:

Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
Комментарии (0)

Я не знаю, что вы считаете патологией, но позвольте мне предложить несколько альтернатив, которые вы могли не видеть раньше:

List sl= list ;
while( ! sl.empty() ) {
    E element= sl.get(0) ;
    .....
    sl= sl.subList(1,sl.size());
}

Или его рекурсивная версия:

void visit(List list) {
    if( list.isEmpty() ) return;
    E element= list.get(0) ;
    ....
    visit(list.subList(1,list.size()));
}

Также рекурсивная версия классического for(int i=0...):

void visit(List list,int pos) {
    if( pos >= list.size() ) return;
    E element= list.get(pos) ;
    ....
    visit(list,pos+1);
}

Я упоминаю их, потому что вы "немного новичок в Java" и это может быть интересно.

Комментарии (5)

Вы можете использовать объекту начиная с Java 8:

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

 nameList.forEach((v) -> System.out.println(v));
Комментарии (0)

В языке Java 8 вы можете использовать список.метод forEach()прилямбда-выражение` для перебора списка. <БР/>

import java.util.ArrayList;
import java.util.List;

public class TestA {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");
        list.forEach(
                (name) -> {
                    System.out.println(name);
                }
        );
    }
}
Комментарии (3)

Право, многие варианты перечислены. Самый простой и чистый будет только с помощью расширенной операторе For, как показано ниже. Выражение это какой-то тип, который является итерируемым.

for ( FormalParameter : Expression ) Statement

Например, чтобы выполнить итерации через список&ЛТ;строка> идентификаторы, мы можем просто так,

for (String str : ids) {
    // Do something
}
Комментарии (1)

На обратный поиск, вы должны использовать следующее:

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

Если вы хотите знать позицию, использовать итератор.previousIndex(). Он также помогает, чтобы написать внутренний цикл, который сравнивает две позиции в списке (итераторы не равны).

Комментарии (0)

Вы всегда можете заменить первый и третий примеры на цикл while и немного больше кода. Это даст вам преимущество в виде возможности использовать do-while:

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

Конечно, такая вещь может вызвать NullPointerException, если list.size() возвращает 0, потому что он всегда выполняется хотя бы один раз. Это можно исправить, проверяя, является ли элемент нулевым, прежде чем использовать его атрибуты / методы. Тем не менее, гораздо проще и легче использовать цикл for.

Комментарии (3)