Ank*_*kur 244 java methods final declaration
编辑:我需要更改几个变量的值,因为它们通过计时器运行几次.我需要通过计时器每次迭代不断更新值.我无法将值设置为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);
}
Run Code Online (Sandbox Code Playgroud)
Jes*_*per 196
Java不支持真正的闭包,即使使用像你在这里使用的匿名类(new TimerTask() { ... })看起来像一种闭包.
编辑 - 请参阅下面的评论 - 以下不是正确的解释,正如KeeperOfTheSoul指出的那样.
这就是为什么它不起作用:
变量lastPrice和价格是main()方法中的局部变量.使用匿名类创建的对象可能会持续到main()方法返回之后.
当main()方法返回时,局部变量(例如lastPrice和price)将从堆栈中清除,因此在main()返回后它们将不再存在.
但是匿名类对象引用了这些变量.如果匿名类对象在清理完变量后尝试访问变量,那将会出现严重错误.
通过制作lastPrice和price final,它们不再是变量,而是常数.然后,编译器可以只使用常量的值替换匿名类中的lastPrice和使用price(当然,在编译时),并且您将不再有访问不存在的变量的问题.
其他支持闭包的编程语言通过特殊处理这些变量来做到这一点 - 通过确保它们在方法结束时不会被销毁,这样闭包仍然可以访问变量.
@Ankur:你可以这样做:
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
// Variables as member variables instead of local variables in main()
private double lastPrice = 0;
private Price priceObject = new Price();
private double price = 0;
public void run() {
price = priceObject.getNextPrice(lastPrice);
System.out.println();
lastPrice = price;
}
}, delay, period);
}
Run Code Online (Sandbox Code Playgroud)
Chr*_*ers 31
为避免奇怪的副作用,由匿名委托引用的java变量中的闭包必须标记为final,因此要lastPrice在计时器任务中引用和定价,需要将它们标记为final.
这显然不适合你,因为你想改变它们,在这种情况下你应该考虑将它们封装在一个类中.
public class Foo {
private PriceObject priceObject;
private double lastPrice;
private double price;
public Foo(PriceObject priceObject) {
this.priceObject = priceObject;
}
public void tick() {
price = priceObject.getNextPrice(lastPrice);
lastPrice = price;
}
}
Run Code Online (Sandbox Code Playgroud)
现在只需创建一个新的Foo作为final,并从计时器调用.tick.
public static void main(String args[]){
int period = 2000;
int delay = 2000;
Price priceObject = new Price();
final Foo foo = new Foo(priceObject);
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
foo.tick();
}
}, delay, period);
}
Run Code Online (Sandbox Code Playgroud)
Rob*_*bin 18
在使用匿名类时,您只能从包含类访问最终变量.因此,您需要声明最终使用的变量(由于您要更改lastPrice和price,因此不适合您),或者不使用匿名类.
因此,您可以选择创建一个实际的内部类,您可以在其中传递变量并以正常方式使用它们
要么:
对你的lastPrice和price变量有一个快速(在我看来很丑陋)的黑客攻击,这就是声明它如此
final double lastPrice[1];
final double price[1];
Run Code Online (Sandbox Code Playgroud)
在您的匿名类中,您可以像这样设置值
price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];
Run Code Online (Sandbox Code Playgroud)
Pet*_*ona 13
很好的解释为什么你不能做你想要做的事已经提供.作为解决方案,可以考虑:
public class foo
{
static class priceInfo
{
public double lastPrice = 0;
public double price = 0;
public Price priceObject = new Price ();
}
public static void main ( String args[] )
{
int period = 2000;
int delay = 2000;
final priceInfo pi = new priceInfo ();
Timer timer = new Timer ();
timer.scheduleAtFixedRate ( new TimerTask ()
{
public void run ()
{
pi.price = pi.priceObject.getNextPrice ( pi.lastPrice );
System.out.println ();
pi.lastPrice = pi.price;
}
}, delay, period );
}
}
Run Code Online (Sandbox Code Playgroud)
似乎你可以做一个比这更好的设计,但想法是你可以将更新的变量分组到一个不会改变的类引用中.
eme*_*ino 10
使用匿名类,您实际上是在声明一个"无名"嵌套类.对于嵌套类,编译器生成一个新的独立公共类,其中包含一个构造函数,该构造函数将其用作参数的所有变量(对于"命名"嵌套类,它始终是原始/封闭类的实例).这样做是因为运行时环境没有嵌套类的概念,因此需要从嵌套类到独立类的(自动)转换.
以此代码为例:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Run Code Online (Sandbox Code Playgroud)
这是行不通的,因为这是编译器在幕后所做的事情:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
Run Code Online (Sandbox Code Playgroud)
原始的匿名类被编译器生成的一些独立类所取代(代码不精确,但应该给你一个好主意):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,独立类包含对共享对象的引用,请记住java中的所有内容都是按值传递的,因此即使EnclosingClass中的引用变量"shared"发生更改,它指向的实例也不会被修改,以及指向它的所有其他引用变量(如匿名类中的那个:Enclosing $ 1),将不会意识到这一点.这是编译器强制您将此"共享"变量声明为final的主要原因,因此这种类型的行为不会使其成为您已经运行的代码.
现在,这是在匿名类中使用实例变量时发生的情况(这是您应该做的解决问题,将逻辑移动到"实例"方法或类的构造函数):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Run Code Online (Sandbox Code Playgroud)
这编译很好,因为编译器会修改代码,因此新生成的类Enclosing $ 1将保存对实例化EnclosingClass实例的引用(这只是一种表示,但应该让你去):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
Run Code Online (Sandbox Code Playgroud)
像这样,当EnclosingClass中的引用变量'shared'被重新分配,而这发生在调用Thread#run()之前,你会看到"其他你好"打印两次,因为现在EnclosingClass $ 1#封闭变量会保留一个引用对于声明它的类的对象,因此对EnclosingClass $ 1的实例可以看到对该对象的任何属性的更改.
有关该主题的更多信息,您可以看到这篇优秀的博文(不是我写的):http://kevinboone.net/java_inner.html
当我偶然发现这个问题时,我只是通过构造函数将对象传递给内部类.如果我需要传递基元或不可变对象(如本例所示),则需要包装类.
编辑:实际上,我根本不使用匿名类,而是使用适当的子类:
public class PriceData {
private double lastPrice = 0;
private double price = 0;
public void setlastPrice(double lastPrice) {
this.lastPrice = lastPrice;
}
public double getLastPrice() {
return lastPrice;
}
public void setPrice(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
}
public class PriceTimerTask extends TimerTask {
private PriceData priceData;
private Price priceObject;
public PriceTimerTask(PriceData priceData, Price priceObject) {
this.priceData = priceData;
this.priceObject = priceObject;
}
public void run() {
priceData.setPrice(priceObject.getNextPrice(lastPrice));
System.out.println();
priceData.setLastPrice(priceData.getPrice());
}
}
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
PriceData priceData = new PriceData();
Price priceObject = new Price();
Timer timer = new Timer();
timer.scheduleAtFixedRate(new PriceTimerTask(priceData, priceObject), delay, period);
}
Run Code Online (Sandbox Code Playgroud)