Нельзя ссылаться на не конечную переменную внутри внутреннего класса, определенного другим методом

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

Ранее я писал то, что ниже:

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

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

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

Ответ 1

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

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

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

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

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

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

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

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

@Ankur: вы можете сделать это:

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

Ответ 2

Чтобы избежать странных побочных эффектов с закрытием в java-переменных, на которые ссылается анонимный делегат, должны быть помечены как окончательные, поэтому, чтобы ссылаться на 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);
}

Ответ 3

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

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

или

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

Возьмите этот код, например:

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 является pass-by-value, поэтому, даже если ссылочная переменная 'shared' в EnclosingClass изменяется, экземпляр, на который он указывает не изменяется, и все остальные ссылочные переменные, указывающие на него (например, в анонимном классе: Enclosing $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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

Однако, если вы пишете общий класс интерфейса, вам нужно передать объект или лучше список объектов. Это может быть сделано с помощью 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));
        }
    });

Ответ 9

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

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

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

 return myvalue.get();

Ответ 10

Одно из решений, о котором я заметил, не упоминается (если я не пропустил его, если бы я исправил меня), это использование переменной класса. В этой проблеме возникла попытка запустить новый поток внутри метода: 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();
    }

}

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

Ответ 15

Основная проблема заключается в том, можно ли разрешить переменную внутри экземпляра анонимного класса во время выполнения. Не обязательно делать переменную final, если она гарантирована, что переменная находится внутри области выполнения. Например, см. Две переменные _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);

                }

            });
        }
    }

}

Ответ 16

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

Перед тем, как основная функция объявит i.e.

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

Ответ 17

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

Ответ 18

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

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

Здесь Output будет

m1 Заканчивается

Работа с потоком

Работа с потоком

Работа с потоком

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

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

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

Ответ 19

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

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

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

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

Ответ 20

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

Просто сделайте окончательные переменные "price" и "lastPrice".

- Изменить

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