Java compiler wrongly marks varible as might already have been assigned to



For some reason java compiler marks the lower str variable as “might already have been assigned to” although this scenario is not possible (at least so I think).
any idea why?
I know removing the final modifier will solve it but I would like to understand the reason…

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

using java 15 on intelliJ IDE

Answer

The rule about not being able to assign to final variables more than once, written more formally (JLS 4.12.4):

It is a compile-time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment.

The Java Language Specification spends the whole chapter 16 talking about what counts as “definitely assigned” and “definitely unassigned”. A variable can be either “definitely assigned” or “definitely unassigned”, both, or neither.

In this case, if you apply the rules about for loops on your code, you will see that there is only one rule that talks about the definite [un]assignment after for loop:

  • V is [un]assigned after a for statement iff both of the following are true:

    • Either a condition expression is not present or V is [un]assigned after the condition expression when false.

    • V is [un]assigned before every break statement for which the for statement is the break target.

Let’s try to show that str is definitely unassigned after the for loop.

In a enchanted for loop, there is obviously a condition expression (checks if the iterator hasNext). The condition doesn’t involve str, so str stays unassigned as before. We meet the first bullet point. str is assigned before the break though, so we don’t meet the second bullet point.

We cannot show that str is definitely unassigned after the for loop using the rules in chapter 16. (In fact we can’t show it is assigned after the for loop either, but that’s rather irrelevant.) The if (!found) statement isn’t doing any assignment either, so this means that str is not definitely unassigned before str = "none";. Hence the compiler error according to 4.12.4.

If you still want to use a final variable, Try using a basic for loop to get the index of the found element, then assign it to 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);
}

Alternatively, use streams:

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


Source: stackoverflow