-1

Working with anonymous innerclasses in java,you have to declare the variables of enclosing class which you are using in the anonymous innerclass as final. Ok i got it that why this must be done from

"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"

This made me to wander that is the final keyword working like as Macros are in C\C++.Till now, I was using final for variables in a way that whenever I will try to modify them(accidentally) I will be getting an error that You can't modify it as it is declared as final.But this replacement thing is not clear to me.

Question: According to the selected answer from the above link answerer says

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.

**

Where this storage is taking place for replacement later?Who is taking care of it?final variables are just replaced by values?

**

Community
  • 1
  • 1
nobalG
  • 4,544
  • 3
  • 34
  • 72

3 Answers3

1

No, this is not like macros in C++. The difference is that macros are evaluated at compile time and the preprocessor replace the macro with its definition.

final variables on the other hand can be computed at run time. Once set, though, the value cannot change at a later time. This constraint is what makes it possible to use the value in an inner class.

Let's look at an example to make this more clear:

public void func(final int param) {
    InnerClass inner = new InnerClass() {
        public void innerFunc() {
            System.out.println(param);
        }
    }

    inner.innerFunc();
}

Note that param can be set at run time by passing different values to it. But each time func() is called, a new InnerClass object is created and captures the current value of param which is guaranteed to never change because it is declared as final.

In a different situation where the variable is constant, then the compiler can replace the value at compile time. However, this isn't special for inner classes because constants are replaced at compile time no matter where they are used.

The moral of the story is that an anonymous inner class can access any final variable whether or not it is a compile time constant or calculated at run time.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
1

With anonymous classes, you are actually declaring a "nameless" nested class. For nested classes, the compiler generates a new standalone public class with a constructor that will take all the variables it uses as arguments (for "named" nested classes, this is always an instance of the original/enclosing class). This is done because the runtime environment has no notion of nested classes, so there needs to be a (automatic) conversion from a nested to a standalone class.

Take this code for example:

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

That won't work, because this is what the compiler does under the hood:

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

The original anonymous class is replaced by some standalone class that the compiler generates (code is not exact, but should give you a good idea):

public class EnclosingClass$1 extends Thread {
    String shared;
    public EnclosingClass$1(String shared) {
        this.shared = shared;
    }

    public void run() {
        System.out.println(shared);
    }
}

As you can see, the standalone class holds a reference to the shared object, remember that everything in java is pass-by-value, so even if the reference variable 'shared' in EnclosingClass gets changed, the instance it points to is not modified, and all other reference variables pointing to it (like the one in the anonymous class: Enclosing$1), will not be aware of this. This is the main reason the compiler forces you to declare this 'shared' variables as final, so that this type of behavior won't make it into your already running code.

Now, this is what happens when you use an instance variable inside an anonymous class (this is what you should do to solve your problem, move your logic to an "instance" method or a constructor of a class):

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

This compiles fine, because the compiler will modify the code, so that the new generated class Enclosing$1 will hold a reference to the instance of EnclosingClass where it was instantiated (this is only a representation, but should get you going):

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

Like this, when the reference variable 'shared' in EnclosingClass gets reassigned, and this happens before the call to Thread#run(), you'll see "other hello" printed twice, because now EnclosingClass$1#enclosing variable will keep a reference to the object of the class where it was declared, so changes to any attribute on that object will be visible to instances of EnclosingClass$1.

For more information on the subject, you can see this excelent blog post (not written by me): http://kevinboone.net/java_inner.html

emerino
  • 1,100
  • 11
  • 17
1

@Butterflow from Brian Goetz:

Declaring a final field helps the optimizer make better optimization decisions, because if the compiler knows the field's value will not change, it can safely cache the value in a register. final fields also provide an extra level of safety by having the compiler enforce that a field is read-only.

You can find here the full article about the keyword final

Alboz
  • 1,833
  • 20
  • 29