Назначение эффективной конечной переменной в операторе try/catch

Следующий код не компилируется с javac 1.8.0_144 и ecj:

private LongSupplier foo() {
    long fileSize;
    try {
        fileSize = canThrow();
    } catch (IOException e) {
        fileSize = 42;
    }

    LongSupplier foo = () -> 1 + fileSize;
    return foo;
}

Мне интересно, если это ошибка в компиляторе. Определение эффективного финала в JLS:

Некоторые переменные, которые не объявлены окончательными, вместо этого считаются фактически окончательными:

  • Локальная переменная, декларатор которой имеет инициализатор (§14.4.2), эффективно является final, если выполняются все следующие условия:

    • Он не объявлен окончательным.

    • Он никогда не встречается в левой части выражения присваивания (§15.26). (Обратите внимание, что декларатор локальной переменной, содержащий инициализатор
      , не является выражением присваивания.)

    • Он никогда не встречается в качестве операнда префиксного или постфиксного оператора увеличения или уменьшения (§15.14, §15.15).

  • Локальная переменная, в объявлении которой отсутствует инициализатор, является окончательной, если выполняются все следующие условия:

    • Он не объявлен окончательным.

    • Всякий раз, когда он встречается в левой части выражения присваивания, он определенно не присваивается и не присваивается определенно перед присваиванием; то есть он определенно не назначен и не определен определенно назначен после правой части выражения присваивания (§16 (Определенное присвоение)).

    • Он никогда не используется в качестве операнда префиксного или постфиксного оператора инкремента или декремента.

  • Метод, конструктор, лямбда-выражение или параметр исключения (§8.4.1, §8.8.1, §9.4, §15.27.1, §14.20) рассматриваются с целью
    определения того, является ли он действительно окончательным, как локальная переменная
    , декларатор которой имеет инициализатор.

Насколько я понимаю, в пункте 2 присваивания в блоке try/catch разрешены, потому что fileSize определенно не присвоено до присваивания.

Я думаю, что аргументация, объясняющая отказ от кода, такова:

  • fileSize определенно не назначен перед блоком try
  • fileSize присваивается (определенно? Кажется, 16.1.8 не заботится об исключениях в присваивании) после fileSize = canThrow()
  • fileSize назначается после блока try
  • fileSize не является однозначно неназначенным перед блоком catch и, следовательно, не является однозначно неназначенным перед назначением в блоке catch.
  • таким образом, пункт 2 пункта 4.12.4 здесь не применяется

Это правильно?


person Jens    schedule 22.12.2017    source источник
comment
Я бы сказал, что fileSize не является фактически окончательным, потому что его можно изменить после исключения fileSize = 0 или иным образом. то есть он не будет компилироваться, если вы сделаете переменную final   -  person Peter Lawrey    schedule 22.12.2017
comment
@PeterLawrey Проблема в том, что javac принимает код, хотя ИМХО не должен. Ни один из пунктов определения здесь не применим, поэтому его следует отклонить, хотя я не вижу технической проблемы, почему это нельзя сделать, когда переменная не присваивается после объявления лямбда. Когда инициация удалена, javac все еще принимает код (что правильно ИМХО), но ecj все равно его отклоняет.   -  person Jens    schedule 22.12.2017
comment
Я согласен с тем, что поведение разумно, хотя и не соответствует спецификации, что означает, что в будущем оно может сломаться.   -  person Peter Lawrey    schedule 22.12.2017
comment
Извините, ребята, я скомпилировал не тот файл. И ecj, и javac отклоняют код, но я не уверен, соответствует ли он JLS.   -  person Jens    schedule 22.12.2017
comment
Я переформулировал вопрос.   -  person Jens    schedule 22.12.2017


Ответы (2)


Определение «Эффективно окончательный» гласит, что добавление модификатора final не должно ничего менять. Давайте сделаем это и получим более четкую ошибку:

error: variable fileSize might already have been assigned
                    fileSize = 42;
                    ^

Таким образом, это точно такой же случай, как Окончательное назначение переменной с помощью try/catch ( который также дает обходной путь с использованием второй конечной переменной), а именно, переменная появляется слева от присваивания, что означает, что она не определенно не назначена.

person Josh Lee    schedule 22.12.2017

(На всякий случай try-catch не имеет ничего общего с проблемой: они просто утверждают, что параметр исключения catch считается окончательным.)

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

Так что они определенно не хотят назначения. Как языковое дизайнерское решение.

Действительно, внутренний поток в canThrow could использует fileSize, который по-прежнему равен 0, после перехвата устанавливает для другой переменной fileSize значение 42. Я думаю, вы считаете, что возникшее исключение указывает на то, что другой поток мертв.

В этом случае вам нужна Future/FutureTask или что-то подобное.

person Joop Eggen    schedule 22.12.2017