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

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

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

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

Это происходит с двойным именем price и Price с именем 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);
}

person Ankur    schedule 19.08.2009    source источник
comment
Я спрашиваю, как мне получить переменную в таймере, которую я могу постоянно обновлять.   -  person Ankur    schedule 19.08.2009
comment
@Ankur: простой ответ - нет. Но вы можете добиться желаемого эффекта, используя внутренний класс; см. ответ @petercardona.   -  person Stephen C    schedule 19.08.2009


Ответы (20)


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

edit - См. комментарии ниже - следующее не является правильным объяснением, как указывает 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);      
}
person Jesper    schedule 19.08.2009
comment
Не совсем верно, Java действительно генерирует захваты для рассматриваемых переменных, чтобы фиксировать их значения во время выполнения, просто они хотели избежать странного побочного эффекта, который возможен в .Net, где вы фиксируете значение в делегате, изменяете значение во внешнем методе, и теперь делегат видит новое значение stackoverflow.com/questions/271440/c-captured-variable-in-loop для примера C # такого поведения, которого Java стремится избежать. - person Chris Chilvers; 19.08.2009
comment
Откуда вы знаете, что Java создает захваты? Насколько я знаю, в этом нет необходимости, если требуется, чтобы локальные переменные были окончательными, так зачем Java генерировать захваты? - person Jesper; 19.08.2009
comment
Он где-то фиксирует значение, даже если оно находится только в частном скрытом поле анонимного класса, поскольку фактические значения могут быть вычислены во время выполнения, а не во время компиляции, что было бы невозможно с реальной константой. - person Chris Chilvers; 19.08.2009
comment
Это не странный побочный эффект, это нормальное поведение, которого люди ожидают, и которое Java не может предоставить , потому что не генерирует захваты. В качестве обходного пути локальные переменные, используемые в анонимном классе, должны быть окончательными. - person Michael Borgwardt; 19.08.2009
comment
Правда, константа не является правильным выражением (но и захват IMO не является). Объект класса Anonymous просто получает копию значения. - person Michael Borgwardt; 19.08.2009
comment
Удивительно, сколько людей, использующих C #, не ожидают этого, я думаю, когда Sun выбирала способ захвата переменных, они решили избежать этого, заставив переменные быть final, что довольно раздражает и приводит к тому, что требуется, чтобы объекты-оболочки получали такой же эффект. - person Chris Chilvers; 19.08.2009
comment
Хммм, ты прав. Я только что сделал небольшую тестовую программу со значением, которое вычисляется во время выполнения, и разобрал ее с помощью javap. Похоже, что значение хранится в переменной-члене анонимного класса, созданной компилятором. - person Jesper; 19.08.2009
comment
Джеспер, вам, вероятно, следует отредактировать неправильные части ваших ответов, а не просто получать сообщение о том, что приведенное выше неверно. - person James McMahon; 05.10.2010
comment
@James Если бы я сделал это, вся дискуссия в комментариях больше не была бы понятна ... Я оставлю это там в этом случае с другим замечанием в верхней части текста. - person Jesper; 07.10.2010
comment
Фактически, Java не поддерживает замыкания. Языки, поддерживающие замыкания, делают это, сохраняя всю локальную среду (то есть набор локальных переменных, определенных в текущем кадре стека) в виде объекта кучи. Java не поддерживает это (разработчики языка хотели реализовать это, но у него не хватило времени), поэтому в качестве обходного пути всякий раз, когда создается экземпляр локального класса, значения любых локальных переменных, на которые он ссылается, копируются в кучу . Однако JVM не может синхронизировать значения с локальными переменными, поэтому они должны быть окончательными. - person Taymon; 04.03.2012
comment
@Taymon Есть ли какой-нибудь соответствующий документ, из которого я могу сослаться на то же самое (ваше объяснение)? - person xyz; 16.01.2013
comment
@Taymon, что именно копируется в кучу? Думаю, все мы хотим знать, глубоко ли клонирован ваш объект или что? - person haelix; 29.08.2013
comment
@haelix Никакие объекты не копируются. Значение каждой конечной локальной переменной сохраняется в экземпляре локального класса. Для непримитивных переменных это значение является ссылкой на объект, а не на весь объект. - person Taymon; 30.08.2013
comment
Этот ответ полностью сбивает с толку, поскольку никто по имени KeeperOfTheSoul не прокомментировал его. Ответ следует доработать. - person Adam Parkin; 07.03.2014
comment
Объяснение (или предыстория) для этого ответа неверно, утверждая, что объекты, созданные внутри метода (локальный контекст), уничтожаются после завершения выполнения метода (и его контекст удаляется из стека) неверно, локальные ПЕРЕМЕННЫЕ будут уничтожены. (удаляется из стека), но объекты, на которые они ссылаются (в случае ссылочных переменных), останутся в куче до тех пор, пока не будет запущен сборщик пакетов и не будет больше сильных ссылок на этот объект. Причина, по которой это на самом деле происходит, связана с тем, как работает компилятор java, см. Мой ответ для объяснения. - person emerino; 20.12.2014
comment
@ChrisChilvers - Я считаю, что избегание странного побочного эффекта было истинной мотивацией выбора Java. Дело не в том, что почему-то Java-дизайнеры были не так умны, как дизайнеры этих других языков :) Недавно для Java 8 проблема была пересмотрена, и изменчивость снова отменена, потому что сначала Брайан Гетц одержим параллельным потоком :) Переменная даже не разрешена чтобы видоизменить объявляемую область действия (т.е. эффективную окончательность), потому что это удивило бы и некоторых других людей. Это финал, точка, избавляющая от многих хлопот. - person ZhongYu; 30.08.2015
comment
Если у вас есть lastPrice внутри такого внутреннего класса, как тогда получить к нему доступ из внешнего класса. После timer.scheduleAtFixedRate(new TimerTask{...}) вы не можете получить доступ к lastPrice, не так ли? - person ; 02.02.2016
comment
@Caketray Вы не можете получить доступ к lastPrice из внешнего класса. Если вам это нужно, просто создайте обычный класс, расширяющий TimerTask, вместо анонимного внутреннего класса. - person Jesper; 04.02.2016
comment
@Taymon, такое объяснение вводит в заблуждение. Практической разницы между реальным замыканием и обходным путем в Java нет. Просто более удобно создавать замыкания на языках, которые лучше поддерживают их. Требование final - неудобство, не более того: оно не означает, что объект не может быть изменен. Утверждение об обратном должно продемонстрировать, что возможно с реальными замыканиями, что невозможно в реализации Java. - person DavidS; 16.03.2016
comment
@Jesper, если это местный класс. тогда как объект класса мог быть там даже после завершения основного метода? - person Asif Mushtaq; 09.06.2016
comment
@UnKnown Локальный класс (определенный внутри метода) может расширять нелокальный класс или реализовывать интерфейс. Вы возвращаете ссылку на экземпляр локального класса из метода, если тип возвращаемого значения метода является суперклассом или интерфейсом, который расширяет локальный класс. - person Jesper; 09.06.2016
comment
@Jesper, почему java не сгенерировал копию локальной переменной во внутренний класс в качестве конечной константы? - person Asif Mushtaq; 09.06.2016
comment
Почему объект внутреннего класса все еще существует, если возвращается основной метод? - person Siva R; 28.02.2017
comment
@shivaR Потому что поток таймера все еще работает и хранит ссылку на объект. - person Jesper; 01.03.2017
comment
@Taymon вы сказали. Однако JVM не может синхронизировать значения с локальными переменными, поэтому они должны быть окончательными. ... под синхронизацией вы имеете в виду, что если локальная переменная будет изменена позже, она также должна быть отражена в их копиях, и поскольку JVM не может этого сделать, поэтому она накладывает ограничение, чтобы сделать ее «окончательной»? (примечание: изменяемая локальная переменная все еще может быть изменена, но поскольку объект совместно используется в куче, изменения будут отражены, поэтому проблем с jvm нет). - person sactiw; 19.04.2017
comment
@ChrisChilvers и другие ... Если пользователь, отправивший ответ, не желает редактировать его, чтобы сделать его правильным, кто-то со знаниями (которые, похоже, есть в ветке комментариев) должен вместо этого отредактировать ответ. У нас не должно быть неправильных ответов на StackExchange, помеченных как правильные. Вот почему функция редактирования ответов открыта для других участников, а не только для первоначального автора. - person authentictech; 19.03.2019

Чтобы избежать странных побочных эффектов с замыканиями в 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);
}
person Chris Chilvers    schedule 19.08.2009
comment
или можно просто под руководством Foo реализовать Runnable ..? - person vidstige; 13.12.2014

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

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

or:

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

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

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

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];
person Robin    schedule 19.08.2009

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

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

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

person Peter Cardona    schedule 19.08.2009

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

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

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 изменяется, экземпляр, на который она указывает, не изменяется. , и все другие ссылочные переменные, указывающие на него (например, в анонимном классе: Enclosing $ 1), не будут знать об этом. Это основная причина, по которой компилятор вынуждает вас объявить эти «общие» переменные как final, чтобы такое поведение не отражалось в вашем уже работающем коде.

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

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 (), вы увидите, что дважды напечатано «other hello», потому что теперь охватывающая переменная EnclosingClass $ 1 # сохранит ссылку к объекту класса, в котором он был объявлен, поэтому изменения любого атрибута этого объекта будут видны экземплярам EnclosingClass $ 1.

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

person emerino    schedule 28.09.2014
comment
Что, если локальная переменная shared является изменяемым объектом? Согласно вашему объяснению, объявление «окончательным» тоже не поможет, верно? - person sactiw; 19.04.2017
comment
Объявление общей переменной как final позволит вам изменить состояние объекта, на который ссылаются конечные переменные, но для этого конкретного примера это не сработает, потому что вы не сможете изменить значение общей переменной (это то, что OP хотел), вы сможете использовать его внутри анонимных классов, но его значение не изменится (потому что оно объявлено окончательным). Важно заметить разницу между переменными и фактическими значениями, которые они содержат (которые могут быть примитивами или ссылками на объекты в куче). - person emerino; 20.04.2017
comment
›››, но это значение не изменится Я думаю, вы упускаете суть, т.е. если последняя ссылочная переменная указывает на изменяемый объект, она все еще может быть обновлена, однако анонимный класс создает мелкую копию таким образом изменения отражаются в анонимном классе. Другими словами, состояния синхронизированы, что здесь и нужно. Здесь OP нуждается в возможности изменять совместно используемую переменную (примитивный тип), и для достижения этого OP необходимо будет заключить значение в изменяемый объект и совместно использовать этот изменяемый объект. - person sactiw; 20.04.2017
comment
Конечно, OP может заключить необходимое значение в изменяемый объект, объявить переменную как final и использовать ее вместо этого. Однако он может избежать использования дополнительного объекта, объявив переменную как атрибут текущего класса (как указано и объяснено в ответе). Принуждение к изменяемым объектам (например, использование массивов только для того, чтобы иметь возможность изменять значение общей переменной) - не лучшая идея. - person emerino; 21.04.2017

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

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

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);
    }
person Buhb    schedule 19.08.2009

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

person Tadeusz Kopec    schedule 19.08.2009

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

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

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

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

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

 return myvalue.get();
person Earth Engine    schedule 16.05.2013

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

}
person James    schedule 23.12.2014

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

person Thorbjørn Ravn Andersen    schedule 19.08.2009

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

person user3251651    schedule 17.08.2014

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

person Abhijay Ghildyal    schedule 02.09.2015
comment
Это на самом деле не отвечает на вопрос ... Это то, почему вас проигрывают. - person Stuart Siegler; 02.09.2015

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

person Greg Mattes    schedule 19.08.2009

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

                }

            });
        }
    }

}
person WaiKit Kung    schedule 23.05.2013

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

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

Double price;
public static void main(String []args(){
--------
--------
}
person punkck    schedule 26.05.2013
comment
Это не сработает, вы объявляете переменную экземпляра, чтобы использовать ее, вам нужно создать экземпляр внутри вашего основного метода. Вы должны быть более конкретными или просто добавить статический модификатор к переменной «цена». - person emerino; 24.12.2014

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

person shweta    schedule 23.09.2014
comment
@Shweta локальные переменные и параметры метода не могут быть объявлены 'статическими', более того, речь идет о том, как это было реализовано, чтобы позволить классам внутри методов (локальным анонимным классам) продолжать доступ к локальным переменным и параметрам метода даже после метода вернул, т.е. он делает их «окончательные» копии и использует их как переменные экземпляра. - person sactiw; 18.06.2015

Еще одно объяснение. Рассмотрим этот пример ниже

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 Завершено

Поток t работает

Поток t работает

Поток t работает

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

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

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

person pks    schedule 05.07.2016

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

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

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

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

person Earth Engine    schedule 19.10.2011

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

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

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

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

person Noon Silk    schedule 19.08.2009
comment
это не просто сбивает с толку - это совершенно неверно, поэтому компилятор не позволяет этого. - person Chii; 19.08.2009
comment
Но тогда как мне изменить значения, когда мне нужно? - person Ankur; 19.08.2009
comment
Не только потому, что это сбивает с толку; это потому, что Java не поддерживает замыкания. Смотрите мой ответ ниже. @Ankur: Вы можете сделать переменные-переменные-члены анонимного объекта класса вместо локальных переменных в main (). - person Jesper; 19.08.2009
comment
Он их изменяет, поэтому они не могут быть окончательными. - person Robin; 19.08.2009
comment
Если цена и lastPrice были окончательными, присваивания им не компилировались. - person Greg Mattes; 19.08.2009
comment
Да, извините, я пропустил это, я предполагаю, что он должен был объявить новые локальные переменные. - person Noon Silk; 19.08.2009
comment
Если бы у Java была лучшая поддержка замыканий, то (в отличие от этого ответа, написанного в настоящее время) изменения были бы подобраны. Есть две наиболее разумные интерпретации, что означает путаницу (и это действительно происходит в реальных примерах). - person Tom Hawtin - tackline; 19.08.2009