Змінна, що використовується в лямбда-експресії, повинна бути остаточною або фактично остаточною


134

Змінна, що використовується в лямбда-експресії, повинна бути остаточною або фактично остаточною

Коли я намагаюся його використовувати 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;
}

5
Ви не можете змінити calTzлямбда.
Елліот Фріш

2
Я припускав, що це одна з тих речей, які просто не були зроблені вчасно для Java 8. Але Java 8 - це 2014 рік. Скала та Котлін дозволяли це роками, тож, очевидно, це можливо. Чи планує Java коли-небудь усунути це дивне обмеження?
GlenPeterson

5
Ось оновлене посилання на коментар @MSDousti.
geisterfurz007

Я думаю, ви могли б використовувати складні ф'ючерси як вирішення.
Kraulain

Одне важливе, що я зауважив - Ви можете використовувати статичні змінні замість звичайних змінних (це робить його ефективно остаточним, я думаю)
kaushalpranav

Відповіді:


68

А 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;
}

Навіть якщо я не розумію деяких фрагментів цього коду:

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

Сподіваюся, що ці поради допоможуть вам покращитись.


ми можемо використовувати не остаточні статичні змінні
Narendra Jaggi

93

Хоча інші відповіді підтверджують вимогу, вони не пояснюють, чому ця вимога існує.

JLS зазначає, чому в § 15.27.2 :

Обмеження ефективних кінцевих змінних забороняє доступ до динамічно мінливих локальних змінних, захоплення яких, ймовірно, спричинить проблеми одночасності.

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


11
Хороша відповідь +1, і я здивований тим, наскільки мало висвітлення отримує причина для ефективного фіналу. Зауважимо: локальна змінна може бути захоплена лямбдаю лише тоді, коли вона також точно визначена перед тілом лямбда. Обидві вимоги, здавалося б, забезпечують безпеку доступу до локальної змінної.
Тім Бігелейзен

2
будь-яка ідея, чому це обмежується лише локальними змінними, а не членами класу? Я часто виявляю цю проблему, оголошуючи свою змінну як члена класу ...
Девід

4
Члени класу @DavidRefaeli охоплені / впливають на модель пам’яті, яка при дотриманні дасть передбачувані результати при спільному використанні. Локальні змінні відсутні, як зазначено у §17.4.1
Діоксин

Це дурний злом, який слід видалити. Компілятор повинен попередити про потенційний доступ до змінної міжпотокової лінії, але повинен дозволити це. Або має бути достатньо розумним, щоб знати, чи працює ваша лямбда на одній нитці, чи працює паралельно тощо. Це дурне обмеження, яке мене сумує. І як уже згадували інші, проблем не існує, наприклад, у C #.
Джош М.

@JoshM. C # також дозволяє створювати типи змінних значень , які люди рекомендують уникати, щоб запобігти проблемам. Замість того, щоб мати такі принципи, Java вирішила повністю запобігти цьому. Це знижує помилки користувачів ціною гнучкості. Я не згоден з цим обмеженням, але це виправдано. Облік паралелізму потребував би додаткової роботи щодо кінця компілятора, імовірно, тому маршрут " попередження перехресного доступу " не був прийнятий. Розробник, який працює над специфікацією, мабуть, буде нашим єдиним підтвердженням для цього.
Діоксин

59

Від лямбда ви не можете отримати посилання на те, що не є остаточним. Вам потрібно оголосити остаточну обгортку поза ламдою, щоб зберегти свою змінну.

Я додав остаточний 'посилальний' об'єкт як цю обгортку.

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();
}   

Я думав про той самий чи подібний підхід - але я хотів би побачити якісь поради / відгуки експертів щодо цієї відповіді?
YoYo

4
Цей код пропускає початковий reference.set(calTz);або посилання має бути створене за допомогою new AtomicReference<>(calTz), інакше ненульова TimeZone, надана як параметр, буде втрачена.
Жульєн Кронегг

8
Це має бути перша відповідь. AtomicReference (або подібний клас Atomic___) працює навколо цього обмеження безпечно за будь-яких можливих обставин.
GlenPeterson

1
Погоджено, це має бути прийнятою відповіддю. Інші відповіді дають корисну інформацію про те, як повернутися до нефункціональної моделі програмування та про те, чому це було зроблено, але насправді не розповідають, як вирішити проблему!
Джонатан Бенн

2
@GlenPeterson, і це теж жахливе рішення, не тільки це набагато повільніше, але ви також ігноруєте властивість побічних ефектів, на які покладено документацію.
Євген

41

У Java 8 є нова концепція, що називається "Ефективно остаточна" змінна. Це означає, що нефінальна локальна змінна, значення якої ніколи не змінюється після ініціалізації, називається "Ефективно кінцевою".

Ця концепція була введена тому, що до Java 8 ми не могли використовувати не остаточну локальну змінну в анонімному класі . Якщо ви хочете отримати доступ до локальної змінної в анонімному класі , ви повинні зробити її остаточною.

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

Java 8 усвідомила, що оголошує локальну змінну остаточним щоразу, коли розробник використовував лямбда, вводив цю концепцію та робив непотрібним робити локальні змінні остаточними. Отже, якщо ви бачите, що правило для анонімних класів не змінилося, просто не потрібно писати finalключове слово щоразу, коли використовуєте лямбда.

Я знайшов гарне пояснення тут


Форматування коду слід використовувати лише для коду , а не для технічних термінів взагалі. effectively finalце не код, це термінологія. Див. Коли слід використовувати форматування коду для тексту без коду? на Meta Stack Overflow .
Чарльз Даффі

(Отже, " finalключове слово" - це слово коду і правильно його форматувати таким чином, але коли ви використовуєте "остаточний" описово, а не як код, натомість це термінологія).
Чарльз Даффі

9

У вашому прикладі ви можете замінити forEachlamdba простим 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];
}

Інший спосіб - використання поля об’єкта. Наприклад, результат MyObj = новий MyObj (); ...; result.timeZone = ...; ....; повернути результат.timezone; Зауважте, що, як було пояснено вище, це створює проблеми з безпекою потоків. Див stackoverflow.com/a/50341404/7092558
Gibezynu Nu

0

якщо не потрібно змінювати змінну, то загальним способом вирішення цього виду проблеми було б вилучення частини коду, яка використовує лямбда і використання остаточного ключового слова методу-параметра.


0

Змінна, що використовується в лямбда-виразі, повинна бути остаточним або фактично остаточним, але ви можете призначити значення остаточному масиву одного елемента.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        TimeZone calTzLocal[] = new TimeZone[1];
        calTzLocal[0] = calTz;
        cal.getComponents().get("VTIMEZONE").forEach(component -> {
            TimeZone v = component;
            v.getTimeZoneId();
            if (calTzLocal[0] == null) {
                calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Це дуже схоже на пропозицію Олександра Удалова. Крім того, я думаю, що цей підхід спирається на побічні ефекти.
Скретт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.