Cannot refer to a non-final variable inside an inner class defined in a different method
Edited: I need to change the values of several variables as they run several times thorugh a timer. I need to keep updating the values with every iteration through the timer. I cannot set the values to final as that will prevent me from updating the values however I am getting the error I describe in the initial question below:
I had previously written what is below:
I am getting the error "cannot refer to a non-final variable inside an inner class defined in a different method".
This is happening for the double called price and the Price called priceObject. Do you know why I get this problem. I do not understand why I need to have a final declaration. Also if you can see what it is I am trying to do, what do I have to do to get around this problem.
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);
}
Solution 1:
Java doesn't support true closures, even though using an anonymous class like you are using here (new TimerTask() { ... }
) looks like a kind of closure.
edit - See the comments below - the following is not a correct explanation, as KeeperOfTheSoul points out.
This is why it doesn't work:
The variables lastPrice
and price are local variables in the main() method. The object that you create with the anonymous class might last until after the main()
method returns.
When the main()
method returns, local variables (such as lastPrice
and price
) will be cleaned up from the stack, so they won't exist anymore after main()
returns.
But the anonymous class object references these variables. Things would go horribly wrong if the anonymous class object tries to access the variables after they have been cleaned up.
By making lastPrice
and price
final
, they are not really variables anymore, but constants. The compiler can then just replace the use of lastPrice
and price
in the anonymous class with the values of the constants (at compile time, of course), and you won't have the problem with accessing non-existent variables anymore.
Other programming languages that do support closures do it by treating those variables specially - by making sure they don't get destroyed when the method ends, so that the closure can still access the variables.
@Ankur: You could do this:
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);
}
Solution 2:
To avoid strange side-effects with closures in java variables referenced by an anonymous delegate must be marked as final, so to refer to lastPrice
and price within the timer task they need to be marked as final.
This obviously won't work for you because you wish to change them, in this case you should look at encapsulating them within a class.
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;
}
}
now just create a new Foo as final and call .tick from the timer.
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);
}