Cannot refer to a non-final variable inside an inner class defined in a different method

前端 未结 20 2275
一向
一向 2020-11-21 05:04

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

20条回答
  •  不知归路
    2020-11-21 05:42

    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

提交回复
热议问题