Невозможно обратиться к неконечной переменной внутри внутреннего класса, определенного в другом методе.

Отредактировано: мне нужно изменить значения нескольких переменных, так как они запускаются несколько раз через таймер. Мне нужно постоянно обновлять значения с каждой итерацией через таймер. Я не могу установить окончательные значения, поскольку это не позволит мне обновить значения, однако я получаю сообщение об ошибке, описанную в исходном вопросе ниже:

Я ранее написал, что ниже:

Я получаю сообщение об ошибке "не может ссылаться на не финальную переменную внутри внутреннего класса, определенного в другом методе".

Это происходит для двойной цены и цены называется ценой объекта. Ты знаешь, почему у меня такая проблема. Я не понимаю, почему мне нужно иметь окончательную декларацию. Также, если вы видите, что я пытаюсь сделать, что мне нужно сделать, чтобы обойти эту проблему.

public static void main(String args[]) {

    int period = 2000;
    int delay = 2000;

    double lastPrice = 0;
    Price priceObject = new Price();
    double price = 0;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);
}

20 ответов

Решение

Java не поддерживает истинные замыкания, хотя и использует анонимный класс, как вы используете здесь (new TimerTask() { ... }) выглядит как своего рода закрытие.

изменить - см. комментарии ниже - следующее не является правильным объяснением, как указывает KeeperOfTheSoul.

Вот почему это не работает:

Переменные lastPrice и цена являются локальными переменными в методе main(). Объект, который вы создаете с помощью анонимного класса, может длиться до main() метод возвращает.

Когда main() метод возвращает локальные переменные (такие как lastPrice а также price) будет очищен из стека, так что они больше не будут существовать после main() возвращается.

Но анонимный объект класса ссылается на эти переменные. Все пошло бы ужасно неправильно, если бы объект анонимного класса попытался получить доступ к переменным после того, как они были очищены.

Делая lastPrice а также pricefinalони больше не переменные, а константы. Компилятор может просто заменить использование lastPrice а также price в анонимном классе со значениями констант (конечно, во время компиляции), и у вас больше не будет проблем с доступом к несуществующим переменным.

Другие языки программирования, которые поддерживают замыкания, делают это путем специальной обработки этих переменных - следя за тем, чтобы они не уничтожались при завершении метода, чтобы замыкание все еще могло обращаться к переменным.

@ Анкур: Вы могли бы сделать это:

public static void main(String args[]) {
    int period = 2000;
    int delay = 2000;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        // Variables as member variables instead of local variables in main()
        private double lastPrice = 0;
        private Price priceObject = new Price();
        private double price = 0;

        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);      
}

Чтобы избежать странных побочных эффектов с замыканиями в java-переменных, на которые ссылается анонимный делегат, необходимо пометить их как final, чтобы обратиться к lastPrice и цена в рамках задания таймера должна быть помечена как окончательная.

Это, очевидно, не будет работать для вас, потому что вы хотите изменить их, в этом случае вы должны смотреть на их инкапсуляцию в классе.

public class Foo {
    private PriceObject priceObject;
    private double lastPrice;
    private double price;

    public Foo(PriceObject priceObject) {
        this.priceObject = priceObject;
    }

    public void tick() {
        price = priceObject.getNextPrice(lastPrice);
        lastPrice = price;
    }
}

Теперь просто создайте новый Foo как final и вызовите.tick из таймера.

public static void main(String args[]){
    int period = 2000;
    int delay = 2000;

    Price priceObject = new Price();
    final Foo foo = new Foo(priceObject);

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            foo.tick();
        }
    }, delay, period);
}

Вы можете получить доступ только к конечным переменным из содержащего класса при использовании анонимного класса. Поэтому вам нужно объявить используемые переменные final (что не подходит для вас, так как вы меняете lastPrice и цену), или не использовать анонимный класс.

Таким образом, вы можете создать реальный внутренний класс, в котором вы можете передавать переменные и использовать их обычным образом.

или же:

Существует быстрый (и, на мой взгляд, некрасивый) хак для вашей переменной lastPrice и цены, который должен объявить это так

final double lastPrice[1];
final double price[1];

и в вашем анонимном классе вы можете установить значение, как это

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];

Хорошие объяснения того, почему вы не можете делать то, что вы пытаетесь сделать, уже предоставлены. В качестве решения, возможно, рассмотрим:

public class foo
{
    static class priceInfo
    {
        public double lastPrice = 0;
        public double price = 0;
        public Price priceObject = new Price ();
    }

    public static void main ( String args[] )
    {

        int period = 2000;
        int delay = 2000;

        final priceInfo pi = new priceInfo ();
        Timer timer = new Timer ();

        timer.scheduleAtFixedRate ( new TimerTask ()
        {
            public void run ()
            {
                pi.price = pi.priceObject.getNextPrice ( pi.lastPrice );
                System.out.println ();
                pi.lastPrice = pi.price;

            }
        }, delay, period );
    }
}

Похоже, что вы могли бы сделать дизайн лучше, чем этот, но идея в том, что вы можете сгруппировать обновленные переменные в ссылку на класс, которая не изменяется.

С анонимными классами вы фактически объявляете "безымянный" вложенный класс. Для вложенных классов компилятор генерирует новый автономный открытый класс с конструктором, который будет принимать все переменные, которые он использует в качестве аргументов (для "именованных" вложенных классов это всегда экземпляр исходного / включающего класса). Это сделано потому, что среда выполнения не имеет представления о вложенных классах, поэтому необходимо выполнить (автоматическое) преобразование из вложенного в автономный класс.

Возьмите этот код для примера:

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

Это не сработает, потому что это то, что компилятор делает изнутри:

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

Исходный анонимный класс заменяется некоторым отдельным классом, который генерирует компилятор (код не является точным, но должен дать вам хорошее представление):

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

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

Как вы можете видеть, автономный класс содержит ссылку на общий объект, помните, что все в java передаются по значению, поэтому даже если переменная ссылки 'shared' в EnclosingClass изменяется, экземпляр, на который он указывает, не изменяется и все другие ссылочные переменные, указывающие на него (как, например, в анонимном классе: с вложением $1), не будут знать об этом. Это основная причина, по которой компилятор вынуждает вас объявлять эти "общие" переменные как окончательные, чтобы этот тип поведения не превращался в уже запущенный код.

Вот что происходит, когда вы используете переменную экземпляра внутри анонимного класса (это то, что вы должны сделать, чтобы решить свою проблему, переместить свою логику в метод "экземпляра" или конструктор класса):

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

Это прекрасно скомпилируется, потому что компилятор изменит код, так что новый сгенерированный класс Enclosing$1 будет содержать ссылку на экземпляр EnclosingClass, в котором он был создан (это только представление, но должно помочь вам):

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

Подобным образом, когда ссылочная переменная 'shared' в EnclosingClass будет переназначена, и это происходит до вызова Thread#run(), вы увидите, что "другой привет" напечатан дважды, потому что теперь вложенная переменная EnclosingClass$1# будет сохранять ссылку для объекта класса, в котором он был объявлен, поэтому изменения любого атрибута этого объекта будут видны экземплярам EnclosingClass$1.

Для получения дополнительной информации по этому вопросу, вы можете увидеть это отличное сообщение в блоге (не написано мной): http://kevinboone.net/java_inner.html

Когда я сталкиваюсь с этой проблемой, я просто передаю объекты во внутренний класс через конструктор. Если мне нужно передать примитивы или неизменяемые объекты (как в этом случае), нужен класс-обертка.

Редактировать: На самом деле я вообще не использую анонимный класс, но правильный подкласс:

public class PriceData {
        private double lastPrice = 0;
        private double price = 0;

        public void setlastPrice(double lastPrice) {
            this.lastPrice = lastPrice;
        }

        public double getLastPrice() {
            return lastPrice;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }
    }

    public class PriceTimerTask extends TimerTask {
        private PriceData priceData;
        private Price priceObject;

        public PriceTimerTask(PriceData priceData, Price priceObject) {
            this.priceData = priceData;
            this.priceObject = priceObject;
        }

        public void run() {
            priceData.setPrice(priceObject.getNextPrice(lastPrice));
            System.out.println();
            priceData.setLastPrice(priceData.getPrice());

        }
    }

    public static void main(String args[]) {

        int period = 2000;
        int delay = 2000;

        PriceData priceData = new PriceData();
        Price priceObject = new Price();

        Timer timer = new Timer();

        timer.scheduleAtFixedRate(new PriceTimerTask(priceData, priceObject), delay, period);
    }

Вы не можете ссылаться на не финальные переменные, потому что так сказано в спецификации языка Java. С 8.1.3:
"Любая локальная переменная, формальный параметр метода или параметр обработчика исключений, используемые, но не объявленные во внутреннем классе, должны быть объявлены окончательными". Весь абзац.
Я могу видеть только часть вашего кода - по моему мнению, планирование изменения локальных переменных - странная идея. Локальные переменные перестают существовать, когда вы покидаете функцию. Может быть, статические поля класса будут лучше?

Вы можете просто объявить переменную вне внешнего класса. После этого вы сможете редактировать переменную из внутреннего класса. Иногда я сталкиваюсь с подобными проблемами при кодировании в Android, поэтому я объявляю переменную глобальной и она работает для меня.

Одно решение, которое я заметил, не упомянуто (если я не пропустил его, если я действительно исправил меня), это использование переменной класса. Столкнулся с этой проблемой, пытаясь запустить новый поток в методе: new Thread(){ Do Something },

призвание doSomething() из следующего будет работать. Вы не обязательно должны объявить это finalПросто нужно изменить область видимости переменной, чтобы она не собиралась до внутреннего класса. Это если, конечно, ваш процесс не огромен и изменение области действия может привести к конфликту. Я не хотел делать мою переменную финальной, поскольку она никоим образом не была финальной / постоянной.

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

}

Я просто написал что-то, чтобы справиться с чем-то по замыслу авторов. Я обнаружил, что лучше всего позволить конструктору взять все объекты, а затем в вашем реализованном методе использовать эти объекты конструктора.

Тем не менее, если вы пишете универсальный интерфейсный класс, то вы должны передать объект, или, лучше, список объектов. Это может быть сделано Object[] или, что еще лучше, Object... потому что его проще вызвать.

Смотри мой пример чуть ниже.

List<String> lst = new ArrayList<String>();
lst.add("1");
lst.add("2");        

SomeAbstractClass p = new SomeAbstractClass (lst, "another parameter", 20, true) {            

    public void perform( ) {                           
        ArrayList<String> lst = (ArrayList<String>)getArgs()[0];                        
    }

};

public abstract class SomeAbstractClass{    
    private Object[] args;

    public SomeAbstractClass(Object ... args) {
        this.args = args;           
    }      

    public abstract void perform();        

    public Object[] getArgs() {
        return args;
    }

}

Пожалуйста, смотрите этот пост о Java-замыканиях, которые поддерживают это из коробки: http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

Версия 1 поддерживает прохождение нефинальных замыканий с автокастингом:
https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/Closure.java

    SortedSet<String> sortedNames = new TreeSet<String>();
    // NOTE! Instead of enforcing final, we pass it through the constructor
    eachLine(randomFile0, new V1<String>(sortedNames) {
        public void call(String line) {
            SortedSet<String> sortedNames = castFirst();  // Read contructor arg zero, and auto cast it
            sortedNames.add(extractName(line));
        }
    });

Если вы хотите изменить значение в вызове метода в анонимном классе, это значение на самом деле Future, Итак, если вы используете Гуава, вы можете написать

...
final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create();
...
someclass.run(new Runnable(){

    public void run(){
        ...
        myvalue.set(value);
        ...
    }
 }

 return myvalue.get();

Используйте ClassName.this.variableName для ссылки на не финальную переменную

Если переменная должна быть конечной, не может быть, тогда вы можете присвоить значение переменной другой переменной и сделать THAT окончательным, чтобы вы могли использовать его вместо этого.

Ты можешь сделать lastPrice, priceObject, а также price поля анонимного внутреннего класса?

Объявите переменную как статическую и ссылайтесь на нее в необходимом методе, используя className.variable

Просто другое объяснение. Рассмотрим этот пример ниже

public class Outer{
     public static void main(String[] args){
         Outer o = new Outer();
         o.m1();        
         o=null;
     }
     public void m1(){
         //int x = 10;
         class Inner{
             Thread t = new Thread(new Runnable(){
                 public void run(){
                     for(int i=0;i<10;i++){
                         try{
                             Thread.sleep(2000);                            
                         }catch(InterruptedException e){
                             //handle InterruptedException e
                         }
                         System.out.println("Thread t running");                             
                     }
                 }
             });
         }
         new Inner().t.start();
         System.out.println("m1 Completes");
    }
}

Здесь вывод будет

m1 завершен

Нить т работает

Нить т работает

Нить т работает

................

Теперь метод m1() завершается, и мы присваиваем ссылочную переменную o пустому значению. Теперь Outer Class Object подходит для GC, но Inner Class Object все еще существует, у которого есть (Has-A) связь с работающим объектом Thread. Без существующего объекта Outer класса нет никаких шансов на существование метода m1(), а без существующего метода m1() нет шансов на существование его локальной переменной, но если Inner Class Object использует локальную переменную метода m1(), то все самоочевидно,

Чтобы решить эту проблему, мы должны создать копию локальной переменной, а затем скопировать ее затем в кучу с объектом класса Inner, что java делает только для конечной переменной, потому что они на самом деле не являются переменными, они как константы (все происходит только во время компиляции не во время выполнения).

Основная проблема заключается в том, может ли переменная внутри экземпляра анонимного класса быть разрешена во время выполнения. Не обязательно делать переменную окончательной, если гарантируется, что переменная находится внутри области выполнения. Например, посмотрите две переменные _statusMessage и _statusTextView внутри метода updateStatus().

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

                }

            });
        }
    }

}

То, что сработало для меня, это просто определить переменную вне этой вашей функции.

Непосредственно перед объявлением основной функции т.е.

Double price;
public static void main(String []args(){
--------
--------
}

Чтобы решить вышеуказанную проблему, разные языки принимают разные решения.

для Java решение - то, что мы видим в этой статье.

для C# решение - разрешить побочные эффекты, и захват по ссылке - единственный вариант.

для C++11 решение состоит в том, чтобы позволить программисту принять решение. Они могут выбрать захват по значению или по ссылке. При захвате по значению никаких побочных эффектов не произойдет, потому что указанная переменная на самом деле отличается. При захвате по ссылке могут возникнуть побочные эффекты, но программист должен это понимать.

Потому что сбивает с толку, если переменная не является окончательной, так как изменения в ней не будут приняты в анонимном классе.

Просто сделайте переменные 'price' и 'lastPrice' final.

-- Редактировать

Упс, и вам также нужно не назначать им, очевидно, в вашей функции. Вам понадобятся новые локальные переменные. В любом случае, я подозреваю, что кто-то уже дал вам лучший ответ.

Другие вопросы по тегам