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

154
02 октября 2019, 18:40

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

List<String> someList = new ArrayList<String>();
//add "monkey", "donkey", "skeleton key" to someList
for(String item : someList) 
  System.out.println(item);

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

Answer 1

Цикл foreach - это синтаксический сахар. Внешне прежде всего отличается от for отсутствием явного счетчика. Единственное практическое различие между ними заключается в том, что в случае индексируемых объектов у вас нет доступа к индексу.

Цикл foreach позволяет выполнять итерации по двум типам объектов:

  • T[] //массивы любого типа
  • Объекты, реализующие интерфейс Iterable.

Цикл for, работающий с объектами Iterable

Код из вопроса

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

равен коду ниже

for (Iterator<E> i = someIterable.iterator(); i.hasNext();) {
    String item = i.next();
    System.out.println(item);
}

Этот код работает для любого объекта, реализующего интерфейс Iterable.

В цикле foreach нельзя использовать метод remove(index). Вместо этого следует использовать iterator.remove(). Пример:

for (Iterator<Integer> 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; 
READ ALSO
Java. read(char [] c, int offset, int len) не срабатывает смещение

Java. read(char [] c, int offset, int len) не срабатывает смещение

Написал метод, который по-идее должен считывать из текстового файла по расположению (path) определенное количество символов (length), со смещением...

113
HashSet и вывод элементов в консоль

HashSet и вывод элементов в консоль

[Petr, Alexandr, Ivan, Dmitriy] вот что я получил в консольПочему вышли значения таким образом, а не по порядку как в ArrayList например?

109
Android: Как лучше отследить объекты?

Android: Как лучше отследить объекты?

Необходимо отследить перемещение одной белой точки на черном фонеИспользую OpenCV и класс CameraPreview на основе SurfaceView

112
Не работает алгоритм A*

Не работает алгоритм A*

Метод получает на вход координаты начальной точки и конечной

137