Будучи немного новым для языка 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
реализация не может "сохранить свое место", чтобы сделать будущие итерации более эффективными. Для a ArrayList
это на самом деле не имеет значения, потому что сложность / стоимость get
является постоянным временем (O (1)), тогда как для a 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 , ...
(Метод карты из Stream API Java 8 (см. Ответ @ i_am_zero).)
В 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).
Какие еще есть способы, если таковые имеются?
(Кстати, мой интерес совсем не связан с желанием оптимизировать производительность ; я просто хочу знать, какие формы доступны мне как разработчику.)
List
интерфейсе конкретно?Ответы:
Три формы зацикливания почти идентичны. Усовершенствованный
for
цикл:является, согласно спецификации языка Java , идентичного по эффекту явного использования итератора с традиционным
for
контуром. В третьем случае вы можете изменить содержимое списка, только удалив текущий элемент, и только тогда, если вы сделаете это черезremove
метод самого итератора. С итерацией на основе индекса вы можете изменять список любым способом. Однако добавление или удаление элементов, предшествующих текущему индексу, может привести к пропуску элементов цикла или обработке одного и того же элемента несколько раз; вам нужно правильно настроить индекс цикла при внесении таких изменений.Во всех случаях
element
это ссылка на фактический элемент списка. Ни один из методов итерации не создает копию чего-либо в списке. Изменения во внутреннем состоянииelement
всегда будут видны во внутреннем состоянии соответствующего элемента в списке.По сути, существует только два способа перебора списка: с помощью индекса или с помощью итератора. Усовершенствованный цикл for - это всего лишь синтаксический ярлык, введенный в Java 5, чтобы избежать скуки явного определения итератора. Для обоих стилей вы можете придумать по существу тривиальные варианты, используя
for
,while
илиdo while
блоки, но все они сводятся к одному и тому же (или, скорее, двум вещам).РЕДАКТИРОВАТЬ: Как @ iX3 указывает в комментарии, вы можете использовать a,
ListIterator
чтобы установить текущий элемент списка во время итерации. Вы должны будете использоватьList#listIterator()
вместоList#iterator()
инициализации переменную цикла (которая, очевидно, должна быть объявленаListIterator
вместо аIterator
).источник
e = iterator.next()
тоe = somethingElse
просто изменяет объект, наe
который ссылается, а не изменяет фактическое хранилище, из которого былоiterator.next()
получено значение.e
не изменит то, что в списке; ты должен позвонитьlist.set(index, thing)
. Вы можете изменить содержимое изe
(например,e.setSomething(newValue)
), но для изменения того, что элемент хранится в списке , как вы итерацию, вы должны придерживаться индекса на основе итерации.e
мне пришлось бы вызывать один изe
методов, потому что присваивание просто меняет указатель (простите мой C).Integer
и,String
было бы невозможно изменить содержимое с помощью методовfor-each
илиIterator
- пришлось бы манипулировать самим объектом списка для замены на элементы. Это правильно?Пример каждого вида, указанного в вопросе:
ListIterationExample.java
источник
Базовый цикл не рекомендуется, так как вы не знаете реализацию списка.
Если это был LinkedList, каждый вызов
будет перебирать список, что приведет к сложности времени N ^ 2.
источник
Итерация в стиле JDK8:
источник
В Java 8 у нас есть несколько способов перебора классов коллекции.
Использование Iterable forEach
Коллекции, которые реализуют
Iterable
(например, все списки), теперь имеютforEach
метод. Мы можем использовать метод-ссылку, представленный в Java 8.Использование потоков для forEach и forEachOrdered
Мы также можем перебрать список, используя Stream как:
Мы должны отдавать предпочтение
forEachOrdered
болееforEach
потому , что поведениеforEach
явно недетерминировано , где , как иforEachOrdered
выполняет действие для каждого элемента этого потока, в порядке столкновения потока , если поток имеет определенный порядок встречи. Таким образом, forEach не гарантирует, что заказ будет сохранен.Преимущество потоков заключается в том, что мы также можем использовать параллельные потоки там, где это необходимо. Если цель состоит только в том, чтобы печатать элементы независимо от порядка, то мы можем использовать параллельный поток как:
источник
Я не знаю, что вы считаете патологическим, но позвольте мне привести некоторые альтернативы, которые вы раньше не видели:
Или его рекурсивная версия:
Также рекурсивный вариант классического
for(int i=0...
:Я упоминаю их, потому что вы «немного новичок в Java», и это может быть интересно.
источник
while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }
. Это более эффективно, чем первая версия;).Вы можете использовать forEach начиная с Java 8:
источник
Для обратного поиска вы должны использовать следующее:
Если вы хотите узнать позицию, используйте iterator.previousIndex (). Это также помогает написать внутренний цикл, который сравнивает две позиции в списке (итераторы не равны).
источник
Да, многие альтернативы перечислены. Самым простым и понятным было бы просто использовать расширенное
for
утверждение, как показано ниже. ЭтоExpression
некоторый тип, который является итеративным.Например, чтобы перебрать идентификаторы List <String>, мы можем просто так,
источник
В
java 8
вы можете использоватьList.forEach()
метод сlambda expression
перебрать список.источник
eugene82
ответа иi_am_zero
ответа ?Вы всегда можете отключить первый и третий примеры с помощью цикла while и немного большего количества кода. Это дает вам возможность использовать do-while:
Конечно, такого рода вещи могут вызвать исключение NullPointerException, если list.size () возвращает 0, потому что он всегда выполняется хотя бы один раз. Это можно исправить, проверив, является ли элемент нулевым, прежде чем использовать его атрибуты / методы tho. Тем не менее, это намного проще и проще использовать цикл for
источник