Ne peut pas se référer à une variable non-finale à l'intérieur d'une classe interne définie dans une autre méthode

StackOverflow https://stackoverflow.com/questions/1299837

Question

Sous la direction: Je dois changer les valeurs de plusieurs variables comme ils courent à plusieurs reprises répercutant une minuterie. Je dois continuer à mettre à jour les valeurs à chaque itération par la minuterie. Je ne peux pas définir les valeurs à la finale car cela me empêchera de mettre à jour les valeurs mais je reçois l'erreur que je décris dans la première question ci-dessous:

Je l'avais déjà écrit ce qui est ci-dessous:

  

Je reçois l'erreur « ne peut pas se référer à une variable non-finale à l'intérieur d'une classe interne définie dans une autre méthode ».

     

Ce qui se passe pour le double prix appelé et le prix appelé priceObject. Savez-vous pourquoi je reçois ce problème. Je ne comprends pas pourquoi je dois avoir une déclaration finale. Aussi, si vous pouvez voir ce qu'il est que je suis en train de faire, que dois-je faire pour contourner ce problème.

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);
}
Était-ce utile?

La solution

Java ne supporte pas les véritables fermetures de rel="noreferrer"> , même si en utilisant une classe anonyme comme vous utilisez ici (new TimerTask() { ... }) ressemble à une sorte de fermeture.

modifier - Voir les commentaires ci-dessous - ce qui suit est pas une explication correcte, comme le souligne KeeperOfTheSoul sur

.

Voilà pourquoi cela ne fonctionne pas:

Les variables lastPrice et le prix sont des variables locales dans la principale méthode (). L'objet que vous créez avec la classe anonyme peut durer jusqu'à après le retour de la méthode de main().

Lorsque la méthode retourne de main(), variables locales (telles que lastPrice et price) seront nettoyés de la pile, de sorte qu'ils n'existeront plus après le retour de main().

Mais l'objet de classe anonyme fait référence ces variables. Les choses seraient horriblement mal tourner si l'objet de classe anonyme tente d'accéder aux variables après avoir été nettoyés.

En faisant lastPrice et price final, ils ne sont pas vraiment plus variables mais constantes. Le compilateur peut alors remplacer simplement l'utilisation de lastPrice et price dans la classe anonyme avec les valeurs des constantes (au moment de la compilation, bien sûr), et vous n'aurez pas le problème avec l'accès à des variables non existantes plus.

D'autres langages de programmation qui font des fermetures de soutien font en traitant ces variables spécialement -. En veillant à ce qu'ils ne soient pas détruits quand la méthode se termine, de sorte que la fermeture peut encore accéder aux variables

@Ankur: Vous pouvez faire ceci:

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

Autres conseils

Pour éviter d'étranges effets secondaires avec des fermetures dans les variables java référencées par un délégué anonyme doit être marqué comme finale, afin de se référer à lastPrice et prix dans la tâche de minuterie, ils doivent être marqués comme définitive.

Cette évidence ne fonctionne pas pour vous parce que vous souhaitez les modifier, dans ce cas, vous devriez regarder les encapsuler dans une classe.

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

juste créer un nouveau Foo comme définitive et appeler .tick de la minuterie.

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

Vous ne pouvez accéder à des variables finales de la classe contenant lors de l'utilisation d'une classe anonyme. Par conséquent, vous devez déclarer les variables utilisées finale (ce qui ne constitue pas une option pour vous puisque vous changez lastPrice et prix ), ou ne pas utiliser une classe anonyme.

Alors vos options pour créer une classe interne réelle, dans laquelle vous pouvez passer dans les variables et les utiliser de façon normale

ou

Il y a un rapide (et à mon avis laid) pirater votre lastPrice et prix variable qui est de déclarer comme si

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

et dans votre classe anonyme vous pouvez définir la valeur comme ceci

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

De bonnes explications pour lesquelles vous ne pouvez pas faire ce que vous essayez de faire déjà fourni. Comme solution, peut-être envisager:

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

On dirait probablement que vous pourriez faire une meilleure conception que cela, mais l'idée est que vous pouvez regrouper les mises à jour des variables à l'intérieur d'une référence de classe qui ne change pas.

Avec les classes anonymes, vous déclarez en fait un « sans nom » classe imbriquée. Pour les classes imbriquées, le compilateur génère une nouvelle classe publique autonome avec un constructeur qui prendra toutes les variables qu'il utilise comme arguments (pour « nommés » classes imbriquées, ce qui est toujours une instance de la classe d'origine / englobante). Ceci est fait parce que l'environnement d'exécution n'a aucune notion de classes imbriquées, donc il doit être une conversion (automatique) à partir d'une imbriqué à une classe autonome.

Prenez ce code par exemple:

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

Cela ne fonctionnera pas, parce que c'est ce que le compilateur fait sous le capot:

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

La classe anonyme d'origine est remplacée par une classe autonome que le compilateur génère (code n'est pas exact, mais devrait vous donner une bonne idée):

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

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

Comme vous pouvez le voir, la classe autonome contient une référence à l'objet partagé, rappelez-vous que tout en Java est passe par valeur, même si la variable de référence « partagée » dans EnclosingClass se change, l'instance il pointe n'est pas modifié, et toutes les autres variables de référence pointant vers elle (comme celui de la classe anonyme: Enfermer 1 $), ne seront pas au courant de cela. C'est la principale raison pour laquelle les forces du compilateur vous de déclarer ces variables « partagées » comme définitive, de sorte que ce type de comportement ne fera pas dans votre code déjà en cours d'exécution.

Maintenant, voici ce qui se passe lorsque vous utilisez une variable d'instance dans une classe anonyme (ce qui est ce que vous devez faire pour résoudre votre problème, déplacez votre logique à une méthode « instance » ou un constructeur d'une classe):

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

Cette compile bien, car le compilateur modifiera le code, de sorte que la nouvelle classe générée Enfermer $ 1 tiendra une référence à l'instance de EnclosingClass où il a été instancié (ce qui est seulement une représentation, mais si vous y aller):

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

Comme cela, lorsque la variable de référence « partagée » dans EnclosingClass obtient réaffectés, et cela se produit avant que l'appel au fil de discussion # run (), vous verrez « autre bonjour » imprimé deux fois, parce que maintenant EnclosingClass $ 1 # enfermant variable ne sera conserver une référence à l'objet de la classe où il a été déclaré, si des modifications à tout attribut sur cet objet seront visibles aux instances de EnclosingClass 1 $.

Pour plus d'informations sur le sujet, vous pouvez voir ce billet de blog excelent (pas écrit par moi): http://kevinboone.net/ java_inner.html

Quand je tombe sur cette question, je viens de passer les objets à la classe interne par le constructeur. Si je dois passer des primitives ou des objets immuables (comme dans ce cas), une classe wrapper est nécessaire.

Edit: En fait, je ne l'utilise pas une classe anonyme du tout, mais une sous-classe appropriée:

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

Vous ne pouvez pas faire référence à des variables non-finales parce que Java Language Specification dit. De 8.1.3:
« Tout paramètre variable locale, paramètre de méthode formelle ou gestionnaire d'exceptions utilisé, mais pas déclarée dans une classe interne doit être déclarée finale. » paragraphe entier.
Je peux voir seulement une partie de votre code - selon moi la programmation modification des variables locales est une idée étrange. Les variables locales cessent d'exister lorsque vous quittez la fonction. Peut-être que les champs statiques d'une classe serait mieux?

Je viens d'écrire quelque chose à gérer quelque chose le long de la auteurs intention . J'ai trouvé la meilleure chose à faire était de laisser la prise constructeur tous les objets puis dans votre procédé mis en oeuvre utiliser que les objets du constructeur.

Cependant, si vous écrivez une classe d'interface générique, vous devez passer un objet, ou mieux une liste d'objets. Cela pourrait se faire par l'objet [] ou mieux encore, Object ... parce qu'il est plus facile d'appeler.

Voir ma pièce par exemple juste au-dessous.

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

}

S'il vous plaît voir ce post sur les fermetures Java qui prend en charge cette sortie de la boîte: http://mseifed.blogspot.se /2012/09/closure-implementation-for-java-5-6-and.html

La version 1 prend en charge le passage des fermetures non-finales avec autocasting:
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));
        }
    });

Si vous souhaitez modifier une valeur dans un appel de méthode au sein d'une classe anonyme, que la « valeur » est en fait un Future. Donc, si vous utilisez goyave, vous pouvez écrire

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

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

 return myvalue.get();

Une solution que je l'ai remarqué est pas mentionné (à moins que je l'ai manqué, si je ne me corrigerez s'il vous plaît), est l'utilisation d'une variable de classe. Ran dans cette question essayant de lancer un nouveau thread dans une méthode: new Thread(){ Do Something }.

L'appel doSomething() de ce qui suit fonctionnera. Vous ne devez pas nécessairement déclarer final, juste besoin de modifier la portée de la variable de sorte qu'il ne soit pas collectée avant la classe interne. Ceci est à moins bien sûr que votre processus est énorme et changer la portée pourrait créer une sorte de conflit. Je ne voulais pas faire ma dernière variable était en aucun cas une finale / constante.

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

}

Si la variable doit être définitive, ne peut pas être alors vous pouvez affecter la valeur de la variable à une autre variable et rendre cette dernière de sorte que vous pouvez l'utiliser à la place.

ClassName.this.variableName utiliser pour référencer la variable non-finale

vous pouvez simplement déclarer la variable en dehors de la classe externe. Après cela, vous pourrez modifier la variable à partir de la classe interne. Je fais face à des problèmes similaires parfois pendant le codage dans Android, donc je déclare la variable globale et il fonctionne pour moi.

Pouvez-vous faire lastPrice, priceObject, et les champs de price de la classe interne anonyme?

La principale préoccupation est de savoir si une variable dans l'instance de classe anonyme peut être résolu lors de l'exécution. Il n'est pas indispensable de faire une dernière variable tant qu'il est garanti que la variable se trouve dans le champ d'exécution. Par exemple, s'il vous plaît voir la méthode deux variables _statusMessage et _statusTextView à l'intérieur 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);

                }

            });
        }
    }

}

ce qui a fonctionné pour moi est juste définir la variable en dehors de cette fonction de votre.

Juste avant la fonction principale déclare i.e..

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

déclarer la variable comme il statique et référence à la méthode désirée en utilisant className.variable

Juste une autre explication. Prenons l'exemple ci-dessous

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

Ici sortie sera

m1 finalise

Discussion t exécutant

Discussion t exécutant

Discussion t exécutant

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

méthode m1 () se termine et nous assigner la variable de référence o null, maintenant externe classe Object est admissible à GC, mais intérieure classe d'objets est existent encore qui a (A-A) relation avec l'objet de cette discussion qui est en cours d'exécution. Sans méthode existante méthode et sans méthode existante m1 () il n'y a aucune chance d'exister sa variable locale objet de la classe externe il n'y a aucune chance de m1 () existante mais si intérieure classe Object utilise la variable locale de m1 (), alors tout est explicite .

Pour résoudre cela, nous devons créer une copie de la variable locale et ensuite de copier ensuite dans le tas avec l'objet de la classe interne, ce java fait pour variable final parce qu'ils ne sont pas variables, ils sont en fait comme des constantes (Tout se passe à compilation ne pas lors de l'exécution).

Pour résoudre le problème ci-dessus, les différentes langues à prendre des décisions différentes.

pour Java, la solution est que ce que nous voyons dans cet article.

pour C #, la solution est de permettre à des effets secondaires et la capture par référence est la seule option.

pour 11 C ++, la solution est de permettre au programmeur de prendre la décision. Ils peuvent choisir de capturer par valeur ou par référence. Si la capture en valeur, pas d'effets secondaires se produisent parce que la variable référencée est en fait différente. Si la capture par référence, les effets secondaires peuvent se produire, mais le programmeur doit le réaliser.

Parce qu'il est source de confusion si la variable est pas définitive, que les modifications apportées à ce ne seront pas ramassés dans la classe anonyme.

Il suffit de faire de prix "variables et 'lastPrice' final.

- Modifier

Oops, et vous aurez également besoin de ne pas céder à eux, évidemment, dans votre fonction. Vous aurez besoin de nouvelles variables locales. Quoi qu'il en soit, je soupçonne que quelqu'un vous a donné une meilleure réponse maintenant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top