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

前端 未结 20 2200
一向
一向 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:39

    One solution I have noticed isn't mentioned (unless I missed it, if I did please correct me), is the use of a class variable. Ran into this issue attempting to run a new thread within a method: new Thread(){ Do Something }.

    Calling doSomething() from the following will work. You do not necessarily have to declare it final, just need to change the scope of the variable so it is not collected before the innerclass. This is unless of course your process is huge and changing the scope might create some sort of conflict. I didn't want to make my variable final as it was in no way a final/constant.

    public class Test
    {
    
        protected String var1;
        protected String var2;
    
        public void doSomething()
        {
            new Thread()
            {
                public void run()
                {
                    System.out.println("In Thread variable 1: " + var1);
                    System.out.println("In Thread variable 2: " + var2);
                }
            }.start();
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 05:40

    use ClassName.this.variableName to reference the non-final variable

    0 讨论(0)
  • 2020-11-21 05:40

    Declare the variable as a static and reference it in the required method using className.variable

    0 讨论(0)
  • 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

    0 讨论(0)
  • 2020-11-21 05:42

    The main concern is whether a variable inside the anonymous class instance can be resolved at run-time. It is not a must to make a variable final as long as it is guaranteed that the variable is inside the run-time scope. For example, please see the two variables _statusMessage and _statusTextView inside updateStatus() method.

    public class WorkerService extends Service {
    
    Worker _worker;
    ExecutorService _executorService;
    ScheduledExecutorService _scheduledStopService;
    
    TextView _statusTextView;
    
    
    @Override
    public void onCreate() {
        _worker = new Worker(this);
        _worker.monitorGpsInBackground();
    
        // To get a thread pool service containing merely one thread
        _executorService = Executors.newSingleThreadExecutor();
    
        // schedule something to run in the future
        _scheduledStopService = Executors.newSingleThreadScheduledExecutor();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
        ServiceRunnable runnable = new ServiceRunnable(this, startId);
        _executorService.execute(runnable);
    
        // the return value tells what the OS should
        // do if this service is killed for resource reasons
        // 1. START_STICKY: the OS restarts the service when resources become
        // available by passing a null intent to onStartCommand
        // 2. START_REDELIVER_INTENT: the OS restarts the service when resources
        // become available by passing the last intent that was passed to the
        // service before it was killed to onStartCommand
        // 3. START_NOT_STICKY: just wait for next call to startService, no
        // auto-restart
        return Service.START_NOT_STICKY;
    }
    
    @Override
    public void onDestroy() {
        _worker.stopGpsMonitoring();
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    class ServiceRunnable implements Runnable {
    
        WorkerService _theService;
        int _startId;
        String _statusMessage;
    
        public ServiceRunnable(WorkerService theService, int startId) {
            _theService = theService;
            _startId = startId;
        }
    
        @Override
        public void run() {
    
            _statusTextView = MyActivity.getActivityStatusView();
    
            // get most recently available location as a latitude /
            // longtitude
            Location location = _worker.getLocation();
            updateStatus("Starting");
    
            // convert lat/lng to a human-readable address
            String address = _worker.reverseGeocode(location);
            updateStatus("Reverse geocoding");
    
            // Write the location and address out to a file
            _worker.save(location, address, "ResponsiveUx.out");
            updateStatus("Done");
    
            DelayedStopRequest stopRequest = new DelayedStopRequest(_theService, _startId);
    
            // schedule a stopRequest after 10 seconds
            _theService._scheduledStopService.schedule(stopRequest, 10, TimeUnit.SECONDS);
        }
    
        void updateStatus(String message) {
            _statusMessage = message;
    
            if (_statusTextView != null) {
                _statusTextView.post(new Runnable() {
    
                    @Override
                    public void run() {
                        _statusTextView.setText(_statusMessage);
    
                    }
    
                });
            }
        }
    
    }
    
    0 讨论(0)
  • 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?

    0 讨论(0)
提交回复
热议问题