问题描述
出于某种原因,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";
}
在 IntelliJ IDE 上使用 java 15
解决方法
编译器不会对您的代码进行深入分析来弄清楚。这将需要很长时间,而且(参见停机问题),完美在数学上是无法实现的。
因此,编译器有一个它应用的简单操作列表。
在这种情况下,str =
赋值位于 if
循环中,因此它可以运行 0 次、1 次或多次。它既不推断设置了 while
的属性(可以运行 0 次),也不允许触及最终变量(可以运行 2 次或更多)。
关于不能多次分配给最终变量的规则,写得更正式(JLS 4.12.4):
如果一个 final 变量被赋值给一个编译时错误,除非它在赋值之前绝对没有被赋值。
Java 语言规范花了整整 chapter 16 篇幅讨论什么算作“确定分配”和“确定未分配”。一个变量可以是“确定分配”或“确定未分配”,两者,或都不是。
在这种情况下,如果您在代码中应用有关 for 循环的规则,您将看到只有一条规则涉及 for 循环后的明确 [un] 赋值:
-
V 是在 for 语句之后 [un] 赋值的,如果以下两个都为真:
-
条件表达式不存在或 V 在条件表达式后 [un] 赋值为 false 时。
-
V 在 for 语句是 break 目标的每个 break 语句之前 [un] 赋值。
-
让我们尝试证明 str
在 for 循环之后肯定是未赋值的。
在一个迷人的 for 循环中,显然有一个条件表达式(检查迭代器是否为 hasNext
)。条件不涉及 str
,因此 str
像以前一样保持未分配状态。我们遇到了第一个要点。 str
是在休息之前分配的,所以我们没有遇到第二个要点。
我们不能使用第 16 章中的规则证明在 for 循环之后 str
肯定是未赋值的。(实际上我们也不能证明它在 for 循环之后被赋值,但这无关紧要。) if (!found)
语句也没有进行任何赋值,因此这意味着 str
not 在 str = "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");
,
鉴于 str 具有修饰符 final 的事实,编译器会查看它,在这种情况下,就好像 str 是一个 String 类型的变量,它将具有更改(给定它的变量)修饰符 final 可以更改的能力不接受更改。