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

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

  Переменная, используемая в лямбда-выражении, должна быть окончательной или фактически окончательной

Когда я пытаюсь использовать calTz, он показывает эту ошибку.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            VTimeZone v = (VTimeZone) component;
            v.getTimeZoneId();
            if (calTz == null) {
                calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}
4b9b3361

Ответ 1

A final означает, что он может быть создан только один раз. в Java вы не можете использовать не конечные переменные в лямбда, а также в анонимных внутренних классах.

Вы можете реорганизовать свой код с помощью старого для каждого цикла:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    try {
        for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(calTz==null) {
               calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
           }
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Даже если я не получу смысл некоторых частей этого кода:

  • вы вызываете v.getTimeZoneId();, не используя его возвращаемое значение
  • с присваиванием calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue()); вы не изменяете первоначально пройденный calTz и не используете его в этом методе
  • Вы всегда возвращаете null, почему бы вам не установить void как возвращаемый тип?

Надеюсь, что эти советы помогут вам улучшить.

Ответ 2

Хотя другие ответы подтверждают требование, они не объясняют, почему требование существует.

JLS упоминает, почему в §15.27.2:

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

Чтобы снизить риск ошибок, они решили, что зафиксированные переменные никогда не будут видоизменяться.

Ответ 3

Из лямбды вы не можете получить ссылку на все, что не является окончательным. Вам нужно объявить окончательную оболочку из-за пределов lamda, чтобы удерживать вашу переменную.

Я добавил конечный объект 'reference' в качестве этой оболочки.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    final AtomicReference<TimeZone> reference = new AtomicReference<>();

    try {
       cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(reference.get()==null) {
               reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
           }
           });
    } catch (Exception e) {
        //log.warn("Unable to determine ical timezone", e);
    }
    return reference.get();
}   

Ответ 4

Java 8 имеет новую концепцию под названием "Эффективно финальная" переменная. Это означает, что неконечная локальная переменная, значение которой никогда не изменяется после инициализации, называется "Эффективно финальной".

Эта концепция была введена потому, что до Java 8 мы не могли использовать нефинальную локальную переменную в анонимном классе. Если вы хотите иметь доступ к локальной переменной в анонимном классе, вы должны сделать ее окончательной.

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

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

Я нашел хорошее объяснение здесь here

Ответ 5

В вашем примере вы можете заменить forEach на lamdba простым циклом for и свободно изменять любую переменную. Или, возможно, рефакторинг вашего кода, чтобы вам не нужно было изменять какие-либо переменные. Однако для полноты поясню, что означает ошибка и как ее обойти.

Спецификация языка Java 8, §15.27.2:

Любая локальная переменная, формальный параметр или параметр исключения, используемые, но не объявленные в лямбда-выражении, должны быть либо объявлены окончательными, либо фактически окончательно (§4.12.4), либо при использовании происходит ошибка времени компиляции попытка.

По сути, вы не можете изменить локальную переменную (в данном случае calTz) из лямбды (или локального/анонимного класса). Чтобы достичь этого в Java, вы должны использовать изменяемый объект и модифицировать его (через конечную переменную) из лямбды. Одним из примеров изменяемого объекта здесь может быть массив из одного элемента:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    TimeZone[] result = { null };
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            ...
            result[0] = ...;
            ...
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return result[0];
}

Ответ 6

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