我正在研究Java内部类,并且遇到与外部方法中的变量引用有关的问题。例如,我有一个源代码来统计排序期间compareTo()
方法被调用的次数:
int counter = 0;
Date[] dates = new Date[100];
for(int i = 0; i < dates.length; i++)
{
dates[i] = new Date()
{
public int compareTo(Date other)
{
counter++;
return super.compareTo(other);
}
};
}
Arrays.sort(dates);
System.out.println(counter + " comparisons");
执行源代码时,您可以看到在使用counter++
时存在错误。为了解决这个问题,有人告诉我,我应该这样改变:
int[] counter = new int[1];
Date[] dates = new Date[100];
for(int i = 0; i < dates.length; i++)
{
dates[i] = new Date()
{
public int compareTo(Date other)
{
counter[0]++;
return super.compareTo(other);
}
};
}
Arrays.sort(dates);
System.out.println(counter[0] + " comparisons");
我很困惑,这两个代码之间有什么区别,这个错误的原因和解决方案是什么?
答案 0 :(得分:2)
您正在创建一段可以“旅行”的代码。 {}
中属于您的new Date()
声明的代码不在您编写的地方运行;它附加到您创建的日期对象上,并随其一起使用。此日期对象可以旅行:可以将其存储在字段中。也许它是从现在开始运行18天,线程完全不同。 VM不知道,因此需要为此做好准备。
因此,我们可以这样说:“计数器”变量会发生什么情况?
通常,局部变量存储在“堆栈上”,并在方法退出时销毁。但是在那种情况下,我们将销毁您的旅行代码仍可访问的变量,那意味着从现在起18天后调用日期compareTo代码是什么意思?
让我们说VM默默地“升级”了变量;与其像在普通栈上那样声明它,不如在堆上声明它,以便变量可以在退出方法时幸免。
好的。如果在另一个线程中调用compareTo怎么办?现在是否可以将局部变量标记为“易失性”?是否可以声明在Java中甚至局部变量也可能显示竞争条件?
这是一个判断电话;语言设计者可以决定的东西。
Java的语言设计人员决定将 against 静默升级到堆中,然后 against 允许本地人可能受到多线程访问。
因此,您在任何可以“移动” *的代码块中访问的任何局部变量都必须声明为[{]]或{B]声明为{A}或[B],就像这样,在这种情况下,java将静默运行为您做最后的决定。
更改是final
(变量本身,不更改)在第二个片段中:它是对数组的引用,并且该引用永不更改。实际上,您已经添加了间接级别,并且可以自己访问堆:数组存在于堆中。
对于它的价值,我发现AtomicX的用法更具可读性。因此,如果您需要一个可以在旅行代码中修改的int,请不要执行counter
;做new int[1]
。如果您需要可修改的字符串,请使用new AtomicInteger
,而不要使用new AtomicReference<String>()
。
NB:是的,在 this 特定代码中,即使在该方法中,即使在进行排序操作时也只能使用counter变量,并且一旦该方法结束,counter var就会消失,但是编译器并没有进行那种非常深入的分析来找出答案,它使用了更简单的规则:是否想在“旅行”代码中从外部脚本访问本地变量?不允许-除非最终(有效)。
*)行进代码是方法本地或匿名类定义中的任何内容,以及lambda中的任何内容。所以:
new String[1]