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

будучи несколько новым для языка 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, эта форма является плохим выбором для перебора Lists, потому что фактическая реализация the get метод может быть не так эффективен, как при использовании Iterator. Например, LinkedList реализации должны пройти все элементы, предшествующие i, чтобы получить i-й элемент.

в приведенном выше примере нет способа для List реализации "сохранить свое место", чтобы сделать будущие итерации более эффективными. Для ArrayList это действительно не имеет значения, потому что сложность/стоимость get - постоянная времени (время O(1)), тогда как для LinkedList пропорционально ли оно размеру списка (O (n)).

для получения дополнительной информации о вычислительной сложности встроенного Collections реализации, проверьте этот вопрос.

Enhanced цикл (хорошо объяснено в этом вопрос)

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.по каждому элементу, поток.по каждому элементу, ...

(метод карты из API потока Java 8 (см. ответ @i_am_zero).)

в классах коллекции Java 8, которые реализуют Iterable (например, all Lists) теперь есть а forEach метод, который можно использовать вместо для оператора цикла показано выше. (Вот еще вопрос это дает хорошее сравнение.)

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

какие еще есть способы, если они есть?

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

11 ответов


три формы зацикливания почти идентичны. Усовершенствованный for петли:

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

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

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

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

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


пример каждого вида, указанного в вопросе:

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

основной цикл не рекомендуется, поскольку вы не знаете реализации списка.

Если это был LinkedList, каждый вызов

list.get(i)

будет повторяться над списком, что приведет к N^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));
    }
}

на Java 8 у нас есть несколько способов перебора классов коллекции.

использование Iterable forEach

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

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

использование потоков forEach и forEachOrdered

мы также можем перебирать список с помощью поток as:

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

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

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", и это может быть интересно.


можно использовать forEach начиная с Java 8:

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

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

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

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

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


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

правильно, перечислены многие альтернативы. Самый простой и чистый будет просто использовать enhanced for заявление, как показано ниже. The Expression имеет некоторый тип, который является итерационным.

for ( FormalParameter : Expression ) Statement

например, чтобы перебрать, перечислить ids, мы можем просто так,

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

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

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

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