Как "final int i" работает внутри цикла Java for?

Я был удивлен, увидев, что следующий фрагмент кода Java скомпилирован и запущен:

for(final int i : listOfNumbers) {
     System.out.println(i);
}

где listOfNumbers - это массив целых чисел.

Я думал, что окончательные объявления получили только один раз. Является ли компилятор, создающий объект Integer и изменяющим то, что он ссылается?

Ответ 1

Представьте, что стенография выглядит примерно так:

for (Iterator<Integer> iter = listOfNumbers.iterator(); iter.hasNext(); )
{
    final int i = iter.next();
    {
        System.out.println(i);
    }
}

Ответ 2

См. @TravisG для объяснения применяемых правил. Единственная причина, по которой вы должны использовать конечную переменную цикла, как это, это если вам нужно закрыть ее в анонимном внутреннем классе.

import java.util.Arrays;
import java.util.List;

public class FinalLoop {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[] { 0,1,2,3,4 });
        Runnable[] runs = new Runnable[list.size()];

        for (final int i : list) {
            runs[i] = new Runnable() {
                    public void run() {
                        System.out.printf("Printing number %d\n", i);
                    }
                };
        }

        for (Runnable run : runs) {
            run.run();
        }
    }
}

Переменная цикла не входит в область Runnable, когда она выполняется, только при ее создании. Без ключевого слова final переменная цикла не будет видна для Runnable, когда она будет запущена. Даже если бы это было так, это было бы одинаковое значение для всех Runnables.

Кстати, около 10 лет назад вы, возможно, видели очень небольшое улучшение скорости при использовании final на локальной переменной (в некоторых редких случаях); что не было в течение долгого времени. Теперь единственная причина использовать final - позволить вам использовать лексическое закрытие, подобное этому.

В ответ на @mafutrct:

Когда вы пишете код, вы делаете это для двух аудиторий. Первый - это компьютер, который до тех пор, пока он синтаксически корректен, будет доволен тем, что вы делаете. Второй - для будущего читателя (часто вы сами), здесь цель состоит в том, чтобы сообщить "намерение" кода, не скрывая "функцию" кода. Языковые функции должны использоваться идиоматически с минимальным количеством интерпретаций, чтобы уменьшить двусмысленность.

В случае переменной цикла использование final может использоваться для связи любой из двух вещей: одно-назначение; или, закрытие. Тривиальное сканирование цикла скажет вам, будет ли переменная цикла переназначена; однако из-за чередования двух путей выполнения при создании замыкания может быть легко пропустить, что закрытие предназначено для захвата переменной. Если, конечно, вы никогда не используете окончательный вариант для обозначения намерения захвата, и в этот момент для читателя становится очевидным, что это то, что происходит.