14

I have button click listener and in onCreate() method I have a local variable like

 onCreate() {

 super.onCreate();

 int i = 10;

 Button button = (Button)findViewById(R.id.button);

 button.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            i++;
        }   
    });

Why java asks for to make me final ?

Khawar Raza
  • 15,870
  • 24
  • 70
  • 127

5 Answers5

29

When the onCreate() method returns, your local variable will be cleaned up from the stack, so they won't exist anymore. But the anonymous class object new View.OnClickListener() references these variables. Of cause it's wrong behavior so java don't allow you to do this.

After it is final it becomes a constant. So it is storing in the heap and can be safely used in anonymous classes.

amukhachov
  • 5,822
  • 1
  • 41
  • 60
  • 1
    Ok fine. The reason you mentioned that i is local, it will be destroyed after onCreate() finishes, so onClickListener() will not be able to get the value of i. But if i is final, then I think, it will destroy too after onCreate() destroys. So how it will be used in onClickListener even it is constant? – Khawar Raza Oct 21 '11 at 09:04
  • 1
    The compiler will just replace the use of i in the anonymous class with the values of the constants at compile time, and you won't have the problem with accessing non-existent variable. – amukhachov Oct 21 '11 at 09:12
  • Is it a special case for final variables? – Khawar Raza Oct 21 '11 at 10:10
  • 1
    No, ofc. As you should know variable in java is just a reference to object. If variable isn't final its reference may be set to null. But when it's final you've got no ability to change it. So this variable would still exist after method returns. – amukhachov Oct 21 '11 at 10:52
13

Your anonymous inner class refers to its enclosing scope by taking copies of the local variables - if you want to change the value of an int in an anonymous inner class, you need to do some hackery:

final int[] arr = new int[1];
arr[0] = 10;
Button button = (Button)findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {
  arr[0]++;
}
hrnt
  • 9,882
  • 2
  • 31
  • 38
13

Because you're accessing it within an anonymous inner class. You can't do that. You can make it final and then read it from the anonymous inner class, but you then can't increment it.

Options:

  • Make it an instance variable of the outer class instead
  • Make it an instance variable of the anonymous inner class instead
  • Use a wrapper - e.g. a single-element array, an AtomicInteger or something like that

I would probably favour the second option unless I needed to get at i from anywhere else. I regard the third option as a bit of a nasty hack, to be honest.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • While other's only gives reasons, only you are giving solutions(thought may be not expected by the person who asked question :)).... hence upvote – Shirish Herwade Feb 02 '15 at 13:22
5

Making the variable final is necessary because under the hood, anonymous inner classes like that are simply syntactic sugar that compile to a nested class outside the scope of the containing method.

This means that none of the variables declared inside of the method are accessible to the inner class, so the compiler pulls another trick - and copies the value in a hidden constructor for your class. To avoid programmer confusion where this copy is not updated to match changes to the variable in the method, it must be final to make sure there are no such changes.

Since your goal here is to have an incrementing integer, and only the reference must be final (the object itself need not be immutable) you could declare a final AtomicInteger i and then increment it as you wish from the callback.

Steven Schlansker
  • 37,580
  • 14
  • 81
  • 100
4

Since you need to increment the i variable you can't make it final. You could make it a class member instead.

dbm
  • 10,376
  • 6
  • 44
  • 56