Способы итерации по списку в 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, мой интерес вовсе не проистекает из желания оптимизировать производительность; я просто хочу знать, какие формы доступны мне как разработчику).
Трех форм цикла почти идентичны. Расширенный цикл
for
:это, согласно спецификацией языка Java, identical в силу явного использования итератора с традиционной
для
петли. В третьем случае, вы можете только изменять содержимое списка путем удаления текущего элемента, а затем только если вы делаете это через "удалить" способ самого итератора. С индексными итерации, вы можете изменить список в любом случае. Однако, добавляя или удаляя элементы, которые приходят до текущих рисков индекса, ваш цикл пропуск элементов или обработки один и тот же элемент несколько раз, нужно правильно отрегулировать цикла, когда вы делаете такие изменения.Во всех случаях, "элемент" является ссылкой на фактический элемент списка. Ни один из методов итерации делает копию чего-либо в списке. Изменения внутреннего состояния "элемент" всегда будет видно внутреннего состояния соответствующего элемента в списке.
По сути, есть только два способа перебора списка: с помощью индекса или с помощью итератора. Улучшенный цикл for-это просто синтаксический ярлык введены в Java 5, чтобы избежать скуки явного задания итератора. Для обоих стилей, можно придумать, казалось бы, тривиальное вариации с помощью
к
,А
илине время
, но все они сводятся к тому же (или, вернее, две вещи).Редактировать: как @iX3 аппликации, отмечает в комментарии, Вы можете использовать ListIterator для текущего элемента списка, как вы итерации. Вы должны использовать
список#listIterator()
вместосписок#итератор()
для инициализации переменной цикла (который, очевидно, должен быть объявленListIterator
, а неитератор
).Пример каждого вида, перечисленного в вопросе:
ListIterationExample.java
Основной цикл не рекомендуется, поскольку вы не знаете осуществления списке.
Если это связанный список LinkedList, каждый вызов
будет перебирать список, в результате чего в N^2 сложность.
В JDK8 стиле итерации:
В в Java 8 мы имеем несколько способов, чтобы выполнить итерации по коллекции классов.
Используя Итерируемому объекту
Коллекции, которые реализуют
итератор
(например, все списки) теперь есть методобъекту
. Мы можем использовать метод Ссылка введены в Java 8.Используя потоки foreach и forEachOrdered
Мы также можем перебрать список, используя Трансляция Как:
Мы должны предпочесть
forEachOrdered
заобъекту
, потому что поведениеобъекту
явно недетерминированные где как forEachOrdered` выполняет действие для каждого элемента этого потока, в поединке того потока, если поток обладает определенными столкнуться с тем. Так что foreach не гарантируем, что заказ будет храниться.Преимущество потоков заключается в том, что мы также можем использовать параллельные потоки по мере необходимости. Если цель состоит только, чтобы печатать предметы независимо от того, то можно использовать параллельный поток, как:
Я не знаю, что вы считаете патологией, но позвольте мне предложить несколько альтернатив, которые вы могли не видеть раньше:
Или его рекурсивная версия:
Также рекурсивная версия классического
for(int i=0...
):Я упоминаю их, потому что вы "немного новичок в Java" и это может быть интересно.
Вы можете использовать объекту начиная с Java 8:
В языке Java 8 вы можете использовать список.метод forEach()
при
лямбда-выражение` для перебора списка. <БР/>Право, многие варианты перечислены. Самый простой и чистый будет только с помощью расширенной операторе For, как показано ниже.
Выражение
это какой-то тип, который является итерируемым.Например, чтобы выполнить итерации через список&ЛТ;строка> идентификаторы, мы можем просто так,
На обратный поиск, вы должны использовать следующее:
Если вы хотите знать позицию, использовать итератор.previousIndex(). Он также помогает, чтобы написать внутренний цикл, который сравнивает две позиции в списке (итераторы не равны).
Вы всегда можете заменить первый и третий примеры на цикл while и немного больше кода. Это даст вам преимущество в виде возможности использовать do-while:
Конечно, такая вещь может вызвать NullPointerException, если list.size() возвращает 0, потому что он всегда выполняется хотя бы один раз. Это можно исправить, проверяя, является ли элемент нулевым, прежде чем использовать его атрибуты / методы. Тем не менее, гораздо проще и легче использовать цикл for.