0
votes

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

3
The compiler does not understand the relationship between the first assignment to str and the setting of found to true. - tgdavies
Well, the compiler won't check your logic so it must assume that you could end up in a situation where str was assigned multiple times. Note that even though your code seems to be simple enough to grasp on a glance the compiler would have to be able to handle much more complex situations which could take a while and still be wrong. So it has to draw a line somewhere ... and your code already crosses it. - Thomas

3 Answers

1
votes

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");
1
votes

The compiler is not going to do a deep analysis on your code to figure it out. It would take a long time, and (see Halting Problem), perfection is literally mathematically unattainable.

Thus, instead, the compiler has a simple list of operations it applies.

In this case, the str = assignment is in an if which is in a while loop, so, it can run 0 times, 1 time, or many times. It neither infers the property that str is set (could run 0 times), but also isn't allowed to touch a final variable (could run 2 times or more).

-3
votes

Given the fact that str has the modifier final, the compiler looks at it, in this scenario as if str is a variable of type String that will has an ability to changes(given its a variable) to which the modifier final can not accept changes.