Java 编译器错误地将变量标记为可能已经分配给

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

出于某种原因 java 编译器将较低的 str 变量标记为“可能已经分配给”,尽管这种情况是不可能的(至少我认为是这样)。
知道为什么吗?
我知道删除 final 修饰符会解决它,但我想了解原因...

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

在 intelliJ 上使用 java 15 IDE

鉴于 str 具有修饰符 final 这一事实,编译器会查看它,在这种情况下,就好像 str 是一个 String 类型的变量,它将能够更改(给定它的变量)修饰符最终无法接受更改。

编译器不会对您的代码进行深入分析来找出答案。这将花费很长时间,而且(请参阅停止问题),完美在数学上几乎是无法实现的。

因此,编译器有一个它应用的简单操作列表。

在这种情况下,str = 赋值是在 if 中,它在 while 循环中,因此,它可以 运行 0 次,1 次,或多次。它既不推断 属性 设置了 str(可能 运行 0 次),也不允许触及最终变量(可能 运行 2 次或更多次).

关于不能多次赋值给最终变量的规则,写得更正式一些(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.

Java 语言规范用了整个 chapter 16 来讨论什么是“明确分配”和“绝对未分配”。变量可以是“明确赋值”或“明确未赋值”,两者,或两者都不.

在这种情况下,如果您在代码中应用有关 for 循环的规则,您将看到只有一条规则讨论 for 循环后的明确 [un] 赋值:

  • 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.

让我们尝试证明 str 在 for 循环之后肯定是未赋值的。

在一个迷人的 for 循环中,显然有一个条件表达式(检查迭代器是否 hasNext)。该条件不涉及 str,因此 str 像以前一样保持未分配状态。我们遇到了第一个要点。 str 是在休息前分配的,所以我们不符合第二个要点。

我们无法使用第 16 章中的规则证明 str 在 for 循环之后绝对未赋值。(事实上我们也无法证明它在 for 循环之后被赋值,但那是无关紧要的。 ) if (!found) 语句也没有做任何赋值,所以这意味着 strstr = "none"; 之前 而不是 绝对未赋值。因此编译器错误根据 4.12.4.

如果您仍想使用 final 变量,请尝试使用基本的 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");