Why can an anonymous class access non-final class member of the enclosing class

2.7k views Asked by At

We know that only final local variables can be accessed in an anonymous class, and there is a good reason here: Why are only final variables accessible in anonymous class?.

However, I found that an anonymous class can still access a non-final variable if the variable is an member field of the enclosing class: How can I access enclosing class instance variables from inside the anonymous class?

I am confused. We ensure that only a final local variable can be accessed in anonymous class because we don't want that the variable should be out-of-sync between anonymous class and local function. The same reason should apply to the case if we try to access a non-final enclosing class member in anonymous class.

Why would it not be a concern?

4

There are 4 answers

2
Yash Sampat On

In the case of a local variable, a copy of the variable is what the anonymous class instance receives. For this reason, the local variable has to be made final before it can be used within the anonymous class, so that its value may not change later.

In the case of a member field of the enclosing class, there is no copy. Rather, the anonymous class gets a reference to the enclosing class, and thereby it accesses any/all member fields and methods of the outer class. So even if the value of the field changes, the change is reflected in the anonymous class as well, because it is the same reference.

I am confused. We ensure that only a final local variable can be accessed in anonymous class because we don't want that the variable should be out-of-sync between anonymous class and local function. The same reason should apply to the case if we try to access a non-final enclosing class member in anonymous class.

As you see, that is not the case. The copying happens only for local variables, not for member fields of the enclosing class. The reason is, of course, that an anonymous class holds an implicit reference to the enclosing class, and through that reference it can access any/all member fields & methods of the outer class.

To quote the link below:

A member variable exists during the lifetime of the enclosing object, so it can be referenced by the inner class instance. A local variable, however, exists only during the method invocation, and is handled differently by the compiler, in that an implicit copy of it is generated as the member of the inner class. Without declaring the local variable final, one could change it, leading to subtle errors due to the inner class still referring to the original value of that variable.

References:

1. Why a non-final “local” variable cannot be used inside an inner class, and instead a non-final field of the enclosing class can?.

2
B. Kemmer On

Non-Static / Inner classes have a reference to there enclosing instance. So they can implicitly refer to instance variables and methods.

If it is a Parameter, even the enclosing class doesn't know anything about it, because it is only accessible from the Method which has defined this variable.

Like Y.S. already pointed out:

In the case of a local variable, a copy of the variable is what the anonymous class instance gets

0
Joop Eggen On

It is more the other way around, a local variable, say x outside the anonymous instance lives as long as the method call. Its object reference is stored on the call stack; x == address on the stack containing an object reference. The x inside the the anonymous instance is a copy as its lifetime is different, longer or even shorter.

As there now are two variables, it was decided not to allow assignment to x (weirdly to implement) and require the variable to be "effectively final."

Access to an outer member, is because the anonymous instance is of an inner class also containing an OuterClass.this reference.

5
Missaka Iddamalgoda On

Consider following example

class InnerSuper{
    void mInner(){}
}
class Outer{
    int aOuter=10;
    InnerSuper mOuter(){
        int aLocal=3999;
        class Inner extends InnerSuper{
            int aInner=20;
            void mInner(){
                System.out.println("a Inner : "+aInner);
                System.out.println("a local : "+aLocal);
            }
        }
        Inner iob=new Inner(); 
        return iob;
    }
}

class Demo{
    public static void main(String args[]){
        Outer ob=new Outer();
        InnerSuper iob=ob.mOuter(); 
        iob.mInner();
    }
}

This won't generate any error in Java 1.8 or above. But in previous version this generates an error asking you to explicitly declare the local variable accessed within the inner class as final. Because what the compiler does is it will keep a copy of the local variable accessed by the inner class so that the copy will exist even if the method/block ends and the local variable goes out of scope. It asks us to declare it final because if the variable changes its value dynamically later in the program after the local inner class or anonymous class is declared, the copy created by the compiler won't change to its new value and could cause problems within the inner class by not producing expected output. Hence it advises us to declare it explicitly final.

But in Java 1.8 it won't generate an error as the compiler declares the accessed local variable implicitly final. It is stated as follows in Java Docs

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final.

Let me explain what is meant by effectively final. Consider the following altered version of the above program

class Outer{
    int aOuter=10;
    InnerSuper mOuter(){
        int aLocal=3999;
        class Inner extends InnerSuper{
            int aInner=20;
            void mInner(){
                System.out.println("a Inner : "+aInner);
                System.out.println("a local : "+aLocal);
            }
        }
        aLocal=4000;
        Inner iob=new Inner(); 
        return iob;
    }
}

Even in Java 1.8 this will produce an error. This is because aLocal is dynamically assigned within the program. This means the variable cannot be considered effectively final by the compiler. As from what I've understood the compiler declares the variables that aren't changed dynamically as final. This is called the variable is effectively final.

Hence it is recommended that you declare the local variables accessed by a Local inner class or an anonymous class explicitly final to avoid any errors.