Страницы

Поиск по вопросам

пятница, 13 декабря 2019 г.

Как работает цикл foreach в Java?

#java #foreach #java_faq


Есть коллекция и цикл foreach:

List someList = new ArrayList();
//add "monkey", "donkey", "skeleton key" to someList

for(String item : someList) 
  System.out.println(item);


Как выглядит цикл for, эквивалентный циклу foreach выше? Как работает цикл foreach
внутри? В чем отличия циклов for и foreach?
    


Ответы

Ответ 1



Цикл foreach - это синтаксический сахар. Внешне прежде всего отличается от for отсутствием явного счетчика. Единственное практическое различие между ними заключается в том, что в случае индексируемых объектов у вас нет доступа к индексу. Цикл foreach позволяет выполнять итерации по двум типам объектов: T[] //массивы любого типа Объекты, реализующие интерфейс Iterable. Цикл for, работающий с объектами Iterable Код из вопроса for(String item : someList) System.out.println(item); равен коду ниже for (Iterator i = someIterable.iterator(); i.hasNext();) { String item = i.next(); System.out.println(item); } Этот код работает для любого объекта, реализующего интерфейс Iterable. В цикле foreach нельзя использовать метод remove(index). Вместо этого следует использовать iterator.remove(). Пример: for (Iterator iterator = list.iterator(); iterator.hasNext(); ) if (iterator.next() > 10) iterator.remove(); Если писать for без использования итератора, то вот примерная реализация foreach: for(int i = 0; i < list.size(); i++) System.out.println(list.get(i)); Циклы foreach и for, работающие с массивами String[] fruits = new String[] { "Orange", "Apple", "Pear", "Strawberry" }; for (String fruit : fruits) { // fruit is an element of the `fruits` array. } по сути эквивалентно for (int i = 0; i < fruits.length; i++) { String fruit = fruits[i]; // fruit is an element of the `fruits` array. } foreach против for - производительность При доступе к коллекции, foreach значительно быстрее, чем for. Однако при доступе к массивам - по крайней мере с массивами примитивов и оболочек - доступ через индексы(т.е. используя for) быстрее. Также при вложенных циклах foreach наблюдаются проблемы с производительностью из-за создания большого количество объектов Iterator. В Java 8 представили потоки, которые в целом работают лучше. (хоть эта информация напрямую и не относится к вопросу, но это может быть полезно) Для работы с коллекций: someList.stream().forEach(System.out::println); Для работы с массивом: Arrays.stream(someArray).forEach(System.out::println); Документация Oracle по foreach. UPD: Измерение производительности на JDK9 (не стоит серьезно его оценивать, т.к. мне не кажется, что я все правильно измерил) Для замера производительности я использовал код из этого вопроса: public static void main(String[] args) { System.out.println(getTime()); } private static void testMethod() { //Код, время выполнения которого нужно узнать } /** * Метод для измерения времени выполнения метода testMethod * https://stackoverflow.com/a/2404378/7150209 */ private static double getTime() { for (int i = 0; i < 20; i ++) { //прогрев JVM testMethod(); } int count = 1000; //первоначальное кол-во повтора выполнения testMethod while(true) { long begin = System.nanoTime(); for (int i = 0; i < count; i ++) testMethod(); long end = System.nanoTime(); if ((end - begin) < 1000000000) { //Прогон тестов пока суммарное выполнения count раз count *= 100000; //testMethod`a не будет равно несколько секунд continue; } return (double)(end - begin) / count; } } Как работает метод getTime() - в цикле for метод с тестируемым кодом запускается count раз. Перед началом запусков засекается время, после всех запусков из конечного времени вычитается начальное - получается время запуска count раз тестового метода. После этого время делится на count - получается усредненное время одного запуска тестового метода. Если время запуска count раз тестового метода < 10 секунд, то count увеличивается, и замер происходит заново. В тестовом методе в циклах for и foreach я использовал: переменная = list.get(i)/array[i] для for и переменная = i; для foreach. В лямбде я использовал Arrays.stream(array).map(l -> l+1).collect(Collectors.toList()); и list.stream().map(l -> l+1).collect(Collectors.toList()), т.е. изменение элементов коллекции и создание новой коллекции, поэтому выполнение лямбд заняло больше времени. В таблице видно, что выполнение в лямбде заняло примерно одинаковое время для коллекций и массивов. Время выполнения кода с массивом примерно в 1.5 раза быстрее, чем с коллекцией. Время выполнения цикла for и foreach для всех одинаковое. P.S. Пример код for и foreach для коллекции(n - просто переменная): for(int i = 0; i < list.size(); i++) n = list.get(i); и for(int i : list) n = i;

Комментариев нет:

Отправить комментарий