views:

4442

answers:

11

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);
}
A: 

Because it's confusing if the variable isn't final, as the changes to it won't be picked up in the anonymous class.

Just make the variables 'price' and 'lastPrice' final.

-- Edit

Oops, and you'll also need to not assign to them, obviously, in your function. You'll need new local variables. Anyway, I suspect someone has given you a better answer by now.

Noon Silk
its not just confusing - its downright incorrect, thus the compiler does not allow it.
Chii
But then how do I change the values when I need to?
Ankur
Not just because it's confusing; this is because Java doesn't support closures. See my answer below. @Ankur: You could make the variables member variables of the anonymous class object instead of local variables in main().
Jesper
He is modifying them, so they cannot be final.
Robin
If price and lastPrice were final, the assignments to them would not compile.
Greg Mattes
Yes, sorry, I missed that, I guess it was implied that he should've declared new local variables.
Noon Silk
If Java had better support for closures, then (contrary to this answer as currently written) the changes would be picked up. There are two mostly reasonable interpretations, which means confusion (and that does happen in real examples).
Tom Hawtin - tackline
+7  A: 

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, ofcourse), 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);      
}
Jesper
Not quite true, Java does generate captures for the variables in question to capture their run-time values, its just they wanted to avoid a strange side-effect that is possible in .Net where by you capture the value in the delegate, change the value in the outer method, and now the delegate sees the new value see, http://stackoverflow.com/questions/271440/c-captured-variable-in-loop for the C# example of this behaviour that Java aims to avoid.
KeeperOfTheSoul
How do you know that Java does generate captures? As far as I know, it isn't necessary to do so if it's required that the local variables should be final, so why would Java generate captures?
Jesper
It captures the value somewhere, even if its just in a private hidden field of the anonymous class, as the actual values can be computed at runtime rather than compile time, which wouldn't be possible with an actual constant.
KeeperOfTheSoul
That's not a "strange side-effect", it's the normal behaviour people would expect - and which Java cannot deliver *because* it does not generate captures. As a workaround, local variables used in an anonymous class must be final.
Michael Borgwardt
True, "constant" is not the correct expression (but neither is IMO "capture"). The Anonymous class object simply gets a copy of the value.
Michael Borgwardt
It's amazing the number of people using C# that don't expect that, I guess when Sun were choosing how to capture variables they decided to avoid that by enforcing the variables be final, which is rather annoying and leads to requiring wrapper objects to get the same effect.
KeeperOfTheSoul
Hmmm, you're right. I just made a small test program with a value that is computed at runtime and disassembled it with javap. It looks like the value is stored in a compiler-generated member variable of the anonymous class.
Jesper
Jesper, you should probably edit out the incorrect parts of your answers rather then just having a message saying the above is incorrect.
James McMahon
@James If I'd do that the whole discussion in the comments wouldn't be understandable anymore... I'll leave it there in this case with another remark at the top of the text.
Jesper
+3  A: 

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);
}
KeeperOfTheSoul
A: 

You can only access final variables from the containing class when using an anonymous class. Therefore you need to declare the variables being used final (which is not an option for you since you are changing lastPrice and price), or don't use an anonymous class.

So your options are to create an actual inner class, in which you can pass in the variables and use them in a normal fashion

or:

There is a quick (and in my opinion ugly) hack for your lastPrice and price variable which is to declare it like so

final double lastPrice[1];
final double price[1];

and in your anonymous class you can set the value like this

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];
Robin
A: 

Can you make lastPrice, priceObject, and price fields of the anonymous inner class?

Greg Mattes
A: 

You cannot refer to non-final variables because Java Language Specification says so. From 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.
I can see only part of your code - according to me scheduling modification of local variables is a strange idea. Local variables cease to exist when you leave the function. Maybe static fields of a class would be better?

Tadeusz Kopec
+2  A: 

Good explanations for why you can't do what you're trying to do already provided. As a solution, maybe consider:

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 );
    }
}

Seems like probably you could do a better design than that, but the idea is that you could group the updated variables inside a class reference that doesn't change.

A: 

When I stumble upon this issue, I just pass the objects to the inner class through the constructor. If I need to pass primitives or immutable objects (as in this case), a wrapper class is needed.

Edit: Actually, I don't use an anonymous class at all, but a proper subclass:

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);
    }
Buhb
A: 

If the variable required to be final, cannot be then you can assign the value of the variable to another variable and make THAT final so you can use it instead.

Thorbjørn Ravn Andersen
A: 

Java Specs: 8.1.3 Inner Classes and Enclosing Instances http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

andres.santana