Подтвердить что ты не робот

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

Будучи несколько новым для языка 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

    // ...
}

Примечание. Как отметил @amarseillan, эта форма является плохим выбором для итерации над List s, потому что фактическая реализация метода get может быть не такой эффективной, как при использовании Iterator. Например, реализация LinkedList должна пересекать все элементы, предшествующие i, чтобы получить i-й элемент.

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

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

Улучшено для цикла (хорошо объяснено в этом вопросе)

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 (см. Ответ @i_am_zero).)

В классах классов Java 8, которые реализуют Iterable (например, все List s), теперь есть метод 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).

Какие существуют другие способы, если они есть?

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

4b9b3361

Ответ 1

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

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

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

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

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

РЕДАКТИРОВАТЬ: Как указывает @iX3 в комментарии, вы можете использовать ListIterator для установки текущего элемента списка по мере повтора. Вам нужно будет использовать List#listIterator() вместо List#iterator() для инициализации переменной цикла (которая, очевидно, должен быть объявлен как ListIterator, а не Iterator).

Ответ 2

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

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

Ответ 3

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

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

list.get(i)

будет итерировать по списку, что приведет к сложности времени N ^ 2.

Ответ 4

Итерация в стиле 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));
    }
}

Ответ 5

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

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

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

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 over forEach потому что поведение forEach явно недетерминировано там, где forEachOrdered выполняет действие для каждого элемента этого потока, в порядке выполнения потока, если поток имеет определенный порядок forEachOrdered. Поэтому forEach не гарантирует, что заказ будет сохранен.

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

Правильно, перечислены многие альтернативы. Самый простой и чистый - это просто использовать расширенный оператор for как показано ниже. Expression имеет некоторый тип, который является итерируемым.

for ( FormalParameter : Expression ) Statement

Например, чтобы перебирать, List <String> ids, мы можем просто так,

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

Ответ 11

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

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

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