Java 为什么这些变量不必是最终变量或有效的最终变量?

Java 为什么这些变量不必是最终变量或有效的最终变量?,java,java-8,Java,Java 8,考虑以下代码: static int k; public static void main(String[] args) { int i = 0; i++; new Thread( () -> {System.out.print(i); }); int[] j = new int[1]; j[0]++; new Thread( () -> {System.out.print(j[0]); }); k = 0; k++;

考虑以下代码:

static int k;
public static void main(String[] args) {
    int i = 0; i++;
    new Thread( () -> {System.out.print(i); });      

    int[] j = new int[1]; j[0]++;
    new Thread( () -> {System.out.print(j[0]); });   

    k = 0; k++;
    new Thread( () -> {System.out.print(k); });      
}
在第一种情况下,我得到一个错误“我在封闭范围内定义的局部变量必须是final或有效final”。我理解这个错误背后的基本原理:lambda表达式中的代码可能在不同的时间运行,其中I的值将不同,因此结果将不同于预期

我不明白的是:为什么我在第二个和第三个例子中没有得到相同的错误,它们实际上是相同的


编辑:关于为什么第一个案例是错误,有很多问题。我的问题不同:我问为什么第二种和第三种情况不被视为错误?

第二个示例没有给出编译器错误,因为它满足有效的最终规则。标识符
j
从未更改,因为它始终引用同一数组,因此您可以在lambda语句块中访问它。执行操作
j[0]+
会改变数组的状态,但不会改变
j
在内存中指向的位置,因此完全可以接受


最后一个示例之所以有效,是因为变量
k
是一个全局变量,它不必是有效的final或final

如果修复了第一个错误,是否会出现预期的第二个错误?如果是,那么可能编译器的错误检测器正在检测所有错误,并抑制它认为可能重复或有噪声的错误。期望的结果是“显示所有相关错误”和“不要用嘈杂的级联错误压倒用户”;由于它们是相反的,因此很难找到一种既能达到目的又能达到目的的算法。捕获的变量(包括局部变量和
this
,实际上是局部变量)必须是最终的或有效的最终的。第一个示例捕获了
inti
,它实际上不是最终的,因此是错误的。第二个捕获
int[]j
,它实际上是final(记住,final变量可以引用可变对象)。第三个没有捕获任何内容,因为
k
不是本地的;它的目的是防止关闭一个可变的本地。它不是为了防止读取或写入可变变量。你明白为什么这些是不同的东西吗?让我们从不同的方向来看待它。描述您将如何设计一个规则,使第二个和第三个案例出错。能否以合理的成本实施?你的规则是否规定一大类完全安全的lambda表达式是非法的?@Erlesegal Halevi它们的寿命不一样。在第一种情况下,局部变量可能会在线程处于活动状态时消失。在第二种情况下,数组是一个对象,线程捕获对它的引用。在第三种情况下,因为它是静态的,所以它的生存期超过了主调用的生存期。你可以为闭包构造另一个语义,但这个语义简单而合理。这个答案在技术上是正确的,但我仍然不明白为什么最后两种情况不被视为错误?虽然它们在技术上与第一个案例不同,但它们的效果完全相同。在第三个例子中,k是静态的。我问这个规则背后的基本原理是什么?你应该问的问题是为什么局部变量必须是“有效的最终变量或最终变量”,但我不明白为什么你认为最后两个例子应该给出编译错误。如果你能提供一个很好的理由说明它会出错,那么我们可以从那里继续下去……因为最后两个例子实际上与第一个完全相同。为什么我使用本地int、数组中的int或静态int很重要?它是相同的整数…@Erlesegal Halevi in C#捕获的变量可以变异,无论是局部变量还是全局变量,一个整数、整数数组、静态整数等等。。。可能还有其他语言,这就是我想说的,所以我说你应该问为什么局部变量必须是“有效的最终或最终的”,而不是你现在所问的,因为我无法理解这个问题。