我正在研究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");
我很困惑这两个代码有什么区别,这个错误的原因是什么以及解决方案?
最佳答案
您正在创建一段可以“旅行”的代码片段。代码在 {}
属于你的new Date()
声明没有在您编写的地方运行;它附加到您创建的日期对象上,并与之一起使用。该日期对象可以移动:它可以存储在字段中。也许它会在 18 天后以完全不同的线程运行。虚拟机不知道,因此需要为此做好准备。
那么假设确实如此:您的“计数器”变量会发生什么?
通常,局部变量存储在“堆栈上”,并在方法退出时被销毁。但在这种情况下,我们会销毁您的旅行代码仍然可以访问的变量,那么从现在起 18 天后,当调用您的 datecompareTo 代码时,这意味着什么呢?
假设虚拟机默默地“升级”了变量;它不像平常那样在堆栈上声明它,而是在堆上声明它,以便变量可以在方法退出后继续存在。
好吧。如果在另一个线程中调用compareTo 会怎样?现在是否可以将局部变量标记为“ volatile ”?可以说,在 java 中,即使是局部变量也可能显示竞争条件吗?
这是一个判断性的决定;由语言设计者决定的事情。
Java 语言设计者决定反对静默升级到堆,并且反对允许局部变量可能受到多线程访问。
因此,您在任何可以“移动”*的代码块中访问的任何局部变量都必须声明为 [A] final
或 [B] 表现得好像它本来可以那样,在这种情况下,java 会默默地为你确定它的最终结果。
变化是counter
,变量本身在第二个片段中不会改变:它是对数组的引用,并且该引用永远不会改变。实际上,您已经自己添加了间接级别和堆访问:数组存在于堆上。
就其值(value)而言,我发现 AtomicX 的用法更具可读性。因此,如果您需要一个可在旅行代码中修改的 int,请不要这样做 new int[1]
;做new AtomicInteger
。如果您需要可修改的字符串,请使用 new AtomicReference<String>()
,不是new String[1]
.
注意:是的,在这个特定代码中,即使排序操作也仅在该方法中使用计数器变量,并且一旦该方法结束,计数器变量就会消失,但是编译器没有进行那种极其深入的分析来弄清楚这一点,它使用更简单的规则:想要从“旅行”代码中的外部 scipe 访问本地变量?不允许 - 除非它是(实际上)最终的。
*) 移动代码是本地方法或匿名类定义中的任何内容,以及 lambda 中的任何内容。所以:
void method() {
class MethodLocalClassDef {
// anything here is considered 'travelling'
}
Object o = new Object() {
// this is an anonymous class def,
// and anything in here is 'travelling'
};
Runnable r = () -> {
// this is a lambda, and considered 'travelling'
};
}
关于java - 从外部方法访问变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63286968/