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

Java для каждого vs regular for - эквивалентны ли они?

Являются ли эти две конструкции эквивалентными?

char[] arr = new char[5];
for (char x : arr) {
    // code goes here
}

По сравнению с:

char[] arr = new char[5];
for (int i = 0; i < arr.length; i++) {
    char x = arr[i];
    // code goes here
}

То есть, , если я помещаю точно такой же код в тело обоих циклов (и компилируется), они будут вести себя точно так же.


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

4b9b3361

Ответ 1

Хотя часто две конструкции взаимозаменяемы, ОНИ НЕ НАХОДЯТСЯ НА 100% ЭКВИВАЛЕНТ!!!

Доказательство можно построить, определив // code goes here, что приведет к тому, что две конструкции будут вести себя по-разному. Одним из таких элементов цикла является:

arr = null;

Поэтому мы теперь сравниваем:

char[] arr = new char[5];
for (char x : arr) {
    arr = null;
}

с:

char[] arr = new char[5];
for (int i = 0; i < arr.length; i++) {
    char x = arr[i];
    arr = null;
}

Оба кода компилируются, но если вы их запустите, вы обнаружите, что первый цикл завершается нормально, а второй цикл будет вызывать NullPointerException.

Это означает, что они не эквивалентны на 100%! Существуют сценарии, в которых две конструкции будут вести себя по-другому!

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


Как добавление, обратите внимание, что иногда для каждой конструкции нет даже варианта, например. если вам нужен индекс. Важнейшим уроком здесь является то, что даже если это опция, вам нужно убедиться, что она фактически эквивалентна замене, потому что она не всегда гарантируется

Аналогично, если вы начинаете с цикла for-each и позже понимаете, что вам нужно переключиться на цикл индексированных, убедитесь, что вы сохраняете семантику, потому что она не гарантируется.

В частности, опасайтесь какой-либо модификации ссылки на массив/коллекцию, которая является итерированной_ (модификация содержимого может/не может инициировать ConcurrentModificationException, но это другая проблема).

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

Ответ 2

Они в значительной степени эквивалентны. Но может быть мало случаев, когда они не являются. Лучше всего сделать это final.

final char[] arr = new char[5];   // Now arr cannot be set null as shown 
                                  // in above answer.

Даже тогда вы можете сделать i-- во втором цикле. Если вы не делаете некоторые из этих маловероятных вещей, они в основном эквивалентны.

Ответ 3

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

Ответ 4

Обратите внимание, что самое важное различие подразумевается ответом, который дал "polygenelubricants", но не указано явно: для каждого итерации через массив, но вы не можете изменить какой-либо элемент, используя экземпляр, который дает цикл вы (в данном случае переменная "char x" ). Классический цикл позволяет вам использовать индекс и изменять элемент массива.

Изменить: быстрая коррекция.