Компилятор Java ошибочно помечает varible как, возможно, уже назначенный

#java #string #compiler-errors #javac #final

Вопрос:

По какой-то причине компилятор java помечает нижнюю переменную str как «возможно, она уже была назначена», хотя этот сценарий невозможен (по крайней мере, я так думаю).
есть идеи, почему?
Я знаю, что удаление последнего модификатора решит эту проблему, но я хотел бы понять причину…

 final List<Long> idList = List.of(1L,2L,3L);
final String str;
boolean found = false;
for (Long id : idList) {
   if (id == 2) {
      str = "id"   id;
      found = true;
      break;
   }
}

if (!found) {
   str = "none";
}
 

использование java 15 в среде IDE IntelliJ

Комментарии:

1. Компилятор не понимает взаимосвязи между первым присвоением str и значением найденного значения true.

2. Ну, компилятор не будет проверять вашу логику, поэтому он должен предположить, что вы можете оказаться в ситуации, когда str вам было назначено несколько раз. Обратите внимание, что, несмотря на то, что ваш код кажется достаточно простым, чтобы понять его с первого взгляда, компилятор должен был бы уметь справляться с гораздо более сложными ситуациями, которые могут занять некоторое время и все равно оказаться неправильными. Поэтому он должен где-то провести черту … и ваш код уже пересекает его.

Ответ №1:

Правило о невозможности присваивать конечным переменным более одного раза, написанное более формально (JLS 4.12.4):

Это ошибка времени компиляции, если конечная переменная назначена, если она определенно не назначена непосредственно перед назначением.

В спецификации языка Java вся глава 16 посвящена тому, что считается «определенно назначенным» и «определенно неназначенным». Переменная может быть либо «определенно назначена», либо «определенно не назначена», и то, и другое, или ни то, ни другое.

В этом случае, если вы примените правила о циклах for в своем коде, вы увидите, что существует только одно правило, которое говорит об определенном [un]назначении после цикла for:

  • V [un]присваивается после оператора for, если оба из следующих значений верны:
    • Либо выражение условия отсутствует, либо V [un]присваивается после выражения условия, если значение false.
    • V присваивается [un]перед каждым оператором разрыва, для которого оператор for является целью разрыва.

Давайте попробуем показать, что str это определенно не назначено после цикла for.

В зачарованном цикле for, очевидно, есть выражение условия (проверяет, работает ли итератор hasNext ). Условие не включает str , поэтому str остается неназначенным, как и раньше. Мы встречаемся с первым пунктом. однако str назначается до перерыва, так что мы не соответствуем второму пункту.

Мы не можем показать, что str это определенно не назначено после цикла for, используя правила в главе 16. (На самом деле мы также не можем показать, что он назначен после цикла for, но это не имеет значения.) if (!found) Оператор также не выполняет никакого назначения, так что это означает, что str он определенно не был назначен ранее str = "none"; . Отсюда ошибка компилятора в соответствии с 4.12.4.

Если вы все еще хотите использовать конечную переменную, попробуйте использовать цикл basic for, чтобы получить индекс найденного элемента, а затем назначить его str :

 final List<Long> idList = List.of(1L,2L,3L);
final String str;
int index = -1;
for (int i = 0; i < idList.size(); i  ) {
  Long id = idList.get(i);
  if (id == 2) {
    index = i;
    break;
  }
}
if (index < 0) {
  str = "none";
} else {
  str = "id"   idList.get(index);
}
 

В качестве альтернативы используйте потоки:

 final String str = 
    idList.stream()
        .filter(x -> x == 2)
        .findFirst()
        .map(x -> "id"   x)
        .orElse("none");
 

Ответ №2:

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

Таким образом, вместо этого компилятор имеет простой список операций, которые он применяет.

В этом случае str = назначение находится в if while цикле, поэтому оно может выполняться 0 раз, 1 раз или много раз. Он не выводит str заданное свойство (может выполняться 0 раз), но также не может касаться конечной переменной (может выполняться 2 или более раз).

Ответ №3:

Учитывая тот факт, что str имеет модификатор final, компилятор рассматривает его в этом сценарии так, как если бы str был переменной типа String, которая будет иметь возможность изменять(учитывая ее переменную), к которой модификатор final не может принимать изменения.