编辑:我需要更改几个变量的值,因为它们通过计时器运行几次 . 我需要通过计时器每次迭代不断更新值 . 我无法将值设置为final,因为这会阻止我更新值,但是我收到了我在下面的初始问题中描述的错误:
我以前写过以下内容:
我收到错误“无法引用在不同方法中定义的内部类中的非final变量” . 这种情况发生在双重调用价格和价格调用priceObject上 . 你知道我为什么会遇到这个问题 . 我不明白为什么我需要最后的声明 . 此外,如果你能看到我想要做的是什么,我该怎么做才能解决这个问题 .
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
double lastPrice = 0;
Price priceObject = new Price();
double price = 0;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
price = priceObject.getNextPrice(lastPrice);
System.out.println();
lastPrice = price;
}
}, delay, period);
}
20 回答
Java不支持true closures,即使使用像你在这里使用的匿名类(
new TimerTask() { ... }
)看起来像一种闭包 .edit - 参见下面的评论 - 以下不是正确的解释,正如KeeperOfTheSoul指出的那样 .
这就是为什么它不起作用:
变量
lastPrice
和price是main()方法中的局部变量 . 使用匿名类创建的对象可能会持续到main()
方法返回之后 .当
main()
方法返回时,局部变量(例如lastPrice
和price
)将从堆栈中清除,因此在main()
返回后它们将不再存在 .但是匿名类对象引用了这些变量 . 如果匿名类对象在清理完变量后尝试访问变量,那将会出现严重错误 .
通过制作
lastPrice
和price
final
,它们不再是变量,而是常量 . 然后,编译器可以将匿名类中的lastPrice
和price
替换为常量的值(当然,在编译时),并且您将不再有访问不存在的变量的问题 .其他支持闭包的编程语言通过特殊处理这些变量来做到这一点 - 通过确保它们在方法结束时不会被销毁,这样闭包仍然可以访问变量 .
@Ankur:你可以这样做:
为了避免使用匿名委托引用的java变量中的闭包产生的奇怪副作用,必须将其标记为final,因此要在计时器任务中引用
lastPrice
和price,需要将它们标记为final .这显然不适合你,因为你想改变它们,在这种情况下你应该考虑将它们封装在一个类中 .
现在只需创建一个新的Foo作为final,并从计时器调用.tick .
在使用匿名类时,您只能从包含类访问最终变量 . 因此,您需要声明最终使用的变量(由于您要更改lastPrice和price,因此不适合您),或者不使用匿名类 .
因此,您可以选择创建一个实际的内部类,您可以在其中传递变量并以正常方式使用它们
要么:
对你的lastPrice和price变量有一个快速(在我看来很丑陋)的黑客攻击,这就是声明它如此
在您的匿名类中,您可以像这样设置值
很好的解释为什么你不能做你想要做的事已经提供 . 作为解决方案,可以考虑:
似乎你可以做一个比这更好的设计,但想法是你可以将更新的变量分组到一个不会改变的类引用中 .
使用匿名类,您实际上是在声明一个“无名”嵌套类 . 对于嵌套类,编译器生成一个新的独立公共类,其中包含一个构造函数,该构造函数将其用作参数的所有变量(对于“命名”嵌套类,它始终是原始/封闭类的实例) . 这样做是因为运行时环境没有嵌套类的概念,因此需要从嵌套类到独立类的(自动)转换 .
以此代码为例:
这是行不通的,因为这是编译器在幕后所做的事情:
原始的匿名类被编译器生成的一些独立类所取代(代码不精确,但应该给你一个好主意):
如您所见,独立类成立对共享对象的引用,请记住java中的所有内容都是按值传递的,因此即使EnclosingClass中的引用变量“shared”发生更改,它指向的实例也不会被修改,所有其他引用变量都指向它(就像匿名类中的那个:Enclosing $ 1),不会意识到这一点 . 这是编译器强制您将此“共享”变量声明为final的主要原因,因此这种类型的行为不会使其成为您已经运行的代码 .
现在,这是在匿名类中使用实例变量时发生的情况(这是您应该做的解决问题,将逻辑移动到“实例”方法或类的构造函数):
这编译很好,因为编译器会修改代码,因此新生成的类Enclosing $ 1将保存对实例化EnclosingClass实例的引用(这只是一种表示,但应该让你去):
像这样,当EnclosingClass中的引用变量'shared'被重新分配,而这发生在调用Thread#run()之前,你会看到“其他你好”打印两次,因为现在EnclosingClass $ 1#封闭变量会保留一个引用对于声明它的类的对象,因此对EnclosingClass $ 1的实例可以看到对该对象的任何属性的更改 .
有关该主题的更多信息,您可以看到这篇优秀的博文(不是我写的):http://kevinboone.net/java_inner.html
当我偶然发现这个问题时,我只是通过构造函数将对象传递给内部类 . 如果我需要传递基元或不可变对象(如本例所示),则需要包装类 .
编辑:实际上,我根本不使用匿名类,而是使用适当的子类:
您不能引用非最终变量,因为Java语言规范是这样说的 . 从8.1.3开始:
"Any local variable, formal method parameter or exception handler parameter used but not declared in an inner class must be declared final." Whole paragraph.
我只能看到你的部分代码 - 根据我的调度修改局部变量是一个奇怪的想法 . 离开函数时,局部变量不再存在 . 也许一个类的静态字段会更好?
我刚刚在 authors intention 写了一些东西到 handle . 我发现最好的办法是让 the constructor take 所有对象然后在你实现的方法中使用该构造函数对象 .
但是,如果您正在编写通用接口类,则必须传递Object或更好的Object对象列表 . 这可以通过Object []甚至更好, Object ... 完成,因为它更容易调用 .
请参阅下面的示例文章 .
请参阅这篇文章,了解支持开箱即用的Java闭包:http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html
版本1支持使用自动展示传递非最终闭包:
https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/Closure.java
如果要在匿名类中更改方法调用中的值,则"value"实际上是
Future
. 所以,如果你使用 Guava ,你可以写我注意到的一个解决方案没有提到(除非我错过了,如果我确实请纠正我),是使用类变量 . 尝试在方法中运行新线程时遇到此问题:
new Thread(){ Do Something }
.从以下调用
doSomething()
将起作用 . 你不一定要声明它final
,只需要改变变量的范围,这样就不会在内部类之前收集它 . 除非你的过程非常庞大,否则改变范围可能会产生某种冲突 . 我不想让我的变量最终,因为它绝不是最终/常数 .如果变量必须是最终的,那么你可以将变量的值赋值给另一个变量并使其成为最终变量,这样你就可以使用它 .
使用ClassName.this.variableName引用非final变量
你可以在外部类之外声明变量 . 在此之后,您将能够在内部类中编辑变量 . 在android中进行编码时,我有时会遇到类似的问题,所以我将变量声明为全局,它对我有用 .
你能制作匿名内部类的
lastPrice
,priceObject
和price
字段吗?主要关注的是是否可以在运行时解析匿名类实例中的变量 . 只要保证变量在运行时范围内,就不必将变量设为final . 例如,请查看两个变量_statusMessage和_statusTextView在updateStatus()方法中 .
对我有用的只是在你的这个函数之外定义变量 .
就在主函数声明之前,即
将变量声明为静态,并使用className.variable在所需方法中引用它
只是另一种解释 . 考虑下面的这个例子
这里输出将是
m1完成
线程运行
线程运行
线程运行
................
现在方法m1()完成,我们将引用变量o分配给null,现在外部类对象符合GC条件但内部类对象仍然存在,它与正在运行的Thread对象具有(Has-A)关系 . 没有现有的外类对象,就没有机会存在m1()方法,没有现有的m1()方法,就没有机会存在它的局部变量,但如果内部类对象使用m1()方法的局部变量那么一切都是自解释的 .
要解决这个问题,我们必须创建一个局部变量的副本,然后必须使用Inner类对象复制到堆中,java只为最终变量做什么,因为它们实际上不是变量,它们就像常量一样(一切只在编译时发生)不是在运行时) .
为了解决上述问题,不同的语言做出不同的决定 .
对于Java,解决方案就像我们在本文中看到的那样 .
对于C#,解决方案是允许副作用并通过引用捕获是唯一的选择 .
对于C 11,解决方案是让程序员做出决定 . 他们可以选择按 Value 或参考进行捕获 . 如果按值捕获,则不会发生副作用,因为引用的变量实际上是不同的 . 如果通过引用捕获,可能会出现副作用,但程序员应该意识到这一点 .
因为如果变量不是最终变量会让人感到困惑,因为对它的更改不会在匿名类中被拾取 .
只需将变量'price'和'lastPrice'设为最终 .
哎呀,显然,你还需要在你的功能中分配它们 . 你需要新的局部变量 . 无论如何,我怀疑有人已经给你一个更好的答案 .