Последний счетчик в цикле for?

У меня есть этот код:

    List<Runnable> r = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        r.add(new Runnable() {

            @Override
            public void run() {
                System.out.println(i);
            }
        });
    }

Он явно не компилируется, потому что i должен быть окончательным, чтобы использоваться в анонимном классе. Но я не могу сделать это окончательным, потому что это не так. Что бы вы сделали? Решение должно дублировать его, но я думал, что может быть лучший способ:

    List<Runnable> r = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        final int i_final = i;
        r.add(new Runnable() {

            @Override
            public void run() {
                System.out.println(i_final);
            }
        });
    }

РЕДАКТИРОВАТЬ, чтобы это было ясно, я использовал Runnable здесь для примера, вопрос действительно об анонимных классах, что может быть чем-то еще.

Ответы

Ответ 1

Я думаю, что ваше решение является самым простым способом.

Другой вариант - реорганизовать создание внутреннего класса в функцию factory, которая сделает это за вас, тогда ваш цикл может быть чем-то чистым:

List<Runnable> r = new ArrayList<>();
for(int i = 0; i < 10; i++) {
    r.add(generateRunnablePrinter(i));
}

И функция factory может просто объявить окончательный параметр:

private Runnable generateRunnablePrinter(final int value) {
    return new Runnable() {
       public void run() {
           System.out.println(value);
       }
    };
}

Я предпочитаю этот рефакторированный подход, потому что он сохраняет код чище, относительно самоописательно, а также скрывает всю внутреннюю печать.

Случайное отступление: если вы считаете, что анонимные внутренние классы эквивалентны замыканиям, то generateRunnablePrinter является фактически функцией более высокого порядка. Кто сказал, что вы не можете выполнять функциональное программирование на Java: -)

Ответ 2

Это то, что IntelliJ делает для вас как исправление. Единственное отличие я бы сделал

ExecutorService es = 
for(int i = 0; i < 10; i++) {
    final int i_final = i;
    es.execute(new Runnable() {

Ответ 3

(Менее оптимальная) альтернатива: создайте небольшой внутренний класс, который реализует Runnable:

class Printer implements Runnable {
    private int index;

    public Printer(int index) {
        this.index = index;
    }

    public void run() {
        System.out.println(index);
    }
}

List<Runnable> r = new ArrayList<>();
for(int i = 0; i < 10; i++) {
    r.add(new Printer(i));
}

Ответ 4

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

BTW: Я предполагаю, что ваш пример является синтетическим, на практике создание нового Runnable просто для печати целого не показалось бы хорошей идеей.

Ответ 5

Я боюсь, что нет другого способа, кроме копирования вашего счетчика на вторую конечную переменную и использования этого в вашем анонимном внутреннем классе. Это один из "недостатков" Java вокруг темы закрытия и рекламируемого преимущества родственных языков, таких как Groovy.

Ответ 6

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

Создание новой конечной локальной переменной является хорошим решением.