web-dev-qa-db-fra.com

Impossible de faire référence à une variable non finale dans une classe interne définie dans une méthode différente

Modifié: Je dois changer les valeurs de plusieurs variables car elles fonctionnent plusieurs fois avec une minuterie. Je dois continuer à mettre à jour les valeurs à chaque itération du minuteur. Je ne peux pas définir les valeurs sur final car cela m'empêchera de mettre à jour les valeurs, mais je reçois l'erreur que je décris dans la question initiale ci-dessous:

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

Je reçois le message d'erreur "ne peut pas faire référence à une variable non finale dans une classe interne définie dans une méthode différente".

Cela se produit pour le prix double appelé et le prix appelé priceObject. Savez-vous pourquoi j'ai ce problème? Je ne comprends pas pourquoi j'ai besoin d'une déclaration finale. Aussi, si vous voyez ce que j'essaie de faire, que dois-je faire pour résoudre 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);
}
242
Ankur

Java ne supporte pas true fermetures , même si utiliser une classe anonyme comme celle que vous utilisez ici (new TimerTask() { ... }) ressemble à une sorte de fermeture.

edit - Voir les commentaires ci-dessous - ce qui suit n'est pas une explication correcte, comme le souligne KeeperOfTheSoul.

C'est pourquoi ça ne marche pas:

Les variables lastPrice et price sont des variables locales dans la méthode main (). L'objet que vous créez avec la classe anonyme peut durer jusqu'au retour de la méthode main().

Lorsque la méthode main() est renvoyée, les variables locales (telles que lastPrice et price) sont supprimées de la pile. Elles n'existeront donc plus après le retour de main().

Mais l'objet de classe anonyme référence ces variables. Les choses iraient terriblement mal si l'objet de classe anonyme essayait d'accéder aux variables une fois qu'elles avaient été nettoyées.

En rendant lastPrice et pricefinal, ils ne sont plus vraiment des variables, mais des constantes. Le compilateur peut alors simplement remplacer l’utilisation de lastPrice et price dans la classe anonyme par les valeurs des constantes (au moment de la compilation, bien sûr), et vous n’aurez pas de problème pour accéder à variables -existent plus.

D'autres langages de programmation qui prennent en charge les fermetures le font en traitant spécialement ces variables - en s'assurant qu'elles ne soient pas détruites à la fin de la méthode, de sorte que la fermeture puisse toujours accéder aux variables.

@Ankur: Vous pourriez 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);      
}
195
Jesper

Pour éviter des effets secondaires étranges avec des fermetures dans Java, les variables référencées par un délégué anonyme doivent être marquées comme étant définitives. Par conséquent, pour faire référence à lastPrice et au prix dans la tâche du minuteur, elles doivent être marquées comme définitives. .

Cela ne fonctionnera évidemment pas pour vous parce que vous souhaitez les modifier. Dans ce cas, vous devriez envisager de 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;
    }
}

maintenant, créez simplement un nouveau Foo en tant que final et appelez .tick à partir 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);
}
30
Chris Chilvers

Vous ne pouvez accéder aux variables finales à partir de la classe contenant que lorsque vous utilisez une classe anonyme. Par conséquent, vous devez déclarer les variables utilisées finales (ce qui n'est pas une option pour vous puisque vous changez lastPrice et price), ou n'utilisez pas de classe anonyme.

Donc, vos options sont de créer une classe interne réelle, dans laquelle vous pouvez passer les variables et les utiliser de manière normale.

ou:

Il y a un hack rapide (et à mon avis moche) pour votre variable lastPrice et price qui consiste à le déclarer comme tel

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

et dans votre classe anonyme, vous pouvez définir la valeur comme celle-ci

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

De bonnes explications sur les raisons pour lesquelles vous ne pouvez pas faire ce que vous essayez de faire déjà fourni. En guise de solution, envisagez peut-être:

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 que vous pourriez probablement faire une meilleure conception que cela, mais l’idée est de regrouper les variables mises à jour dans une référence de classe qui ne change pas.

13
Peter Cardona

Avec les classes anonymes, vous déclarez en fait une classe imbriquée "sans nom". Pour les classes imbriquées, le compilateur génère une nouvelle classe publique autonome avec un constructeur qui prend toutes les variables utilisées en tant qu'arguments (pour les classes imbriquées "nommées", il s'agit toujours d'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, il doit donc y avoir une conversion (automatique) d'une classe imbriquée à une classe autonome.

Prenons 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, car 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 originale est remplacée par une classe autonome générée par le compilateur (le 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 constater, la classe autonome contient une référence à l'objet partagé. N'oubliez pas que tout ce qui se trouve dans Java est transmis selon la valeur. Ainsi, même si la variable de référence 'shared' dans EnclosingClass est modifiée, l'instance il pointe vers n'est pas modifié et toutes les autres variables de référence pointant vers elle (comme celle de la classe anonyme: englobant $ 1), ne le sauront pas. C'est la raison principale pour laquelle le compilateur vous oblige à déclarer cette variable 'partagée' comme finale, afin que ce type de comportement ne soit pas intégré 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 (c'est ce que vous devez faire pour résoudre votre problème, déplacez votre logique vers une méthode "instance" ou un constructeur de 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);
    }
}

Cela compile très bien, car le compilateur modifiera le code, de sorte que la nouvelle classe générée Enclosing $ 1 contiendra une référence à l'instance de EnclosingClass où elle a été instanciée (il ne s'agit que d'une représentation, mais devrait vous permettre de continuer):

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

De même, lorsque la variable de référence 'shared' dans EnclosingClass est réaffectée, et ceci avant l'appel à Thread # run (), vous verrez "other hello" imprimé deux fois, car à présent la variable englobante EnclosingClass $ 1 # conservera une référence. à l'objet de la classe où elle a été déclarée, ainsi les modifications apportées à tout attribut sur cet objet seront visibles pour les instances de EnclosingClass $ 1.

Pour plus d'informations sur le sujet, vous pouvez voir cet excellent blog (non écrit par moi): http://kevinboone.net/Java_inner.html

10
emerino

Lorsque je tombe sur cette question, je passe simplement les objets à la classe interne par le biais du constructeur. Si j'ai besoin de passer des primitives ou des objets immuables (comme dans ce cas), une classe wrapper est nécessaire.

Edit: En fait, je n’utilise pas du tout une classe anonyme, 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);
    }
7
Buhb

Une solution que j’ai remarquée n’est pas mentionnée (à moins que je ne l’aie manquée, s’il me faut, corrigez-moi), est l’utilisation d’une variable de classe. Couru dans ce problème en essayant d'exécuter un nouveau thread dans une méthode: new Thread(){ Do Something }.

L'appel de doSomething() parmi les suivants fonctionnera. Vous n'êtes pas obligé de le déclarer final, il vous suffit de modifier l'étendue de la variable pour qu'elle ne soit pas collectée avant la classe intérieure. C’est à moins que, bien sûr, votre processus soit énorme et que changer la portée puisse créer une sorte de conflit. Je ne voulais pas rendre ma variable finale car ce n'é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();
    }

}
2
James

Je viens d'écrire quelque chose pour gérer quelque chose le long de l'intention de l'auteur . J'ai trouvé que la meilleure chose à faire était de laisser le constructeur prendre tous les objets, puis utiliser les objets du constructeur dans votre méthode implémentée.

Toutefois, si vous écrivez une classe d'interface générique, vous devez alors transmettre un objet, ou mieux une liste d'objets. Cela pourrait être fait par Object [] ou même mieux Object ... car il est plus facile d'appeler.

Voir mon exemple ci-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;
    }

}

Veuillez consulter ce message à propos des Java fermetures prenant en charge cette configuration: 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 à la diffusion automatique:
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));
        }
    });
2
momomo

Vous ne pouvez pas faire référence à des variables non finales, car Java Spécification de langage l'indique. À partir de 8.1.3:
"Toute variable locale, paramètre de méthode formelle ou paramètre de gestionnaire d'exceptions utilisé mais non déclaré dans une classe interne doit être déclaré final." Tout le paragraphe.
Je ne vois qu'une partie de votre code. Selon moi, la modification de la planification 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 seraient meilleurs?

2
Tadeusz Kopec

Si vous souhaitez modifier une valeur dans un appel de méthode dans une classe anonyme, cette "valeur" est en fait un Future. Donc, si vous utilisez Guava, vous pouvez écrire

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

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

 return myvalue.get();
2
Earth Engine

Si la variable requise pour être finale ne peut pas l'être, vous pouvez alors affecter la valeur de la variable à une autre variable et rendre CE final afin que vous puissiez l'utiliser à la place.

1

vous pouvez simplement déclarer la variable en dehors de la classe externe. Après cela, vous pourrez éditer la variable depuis la classe interne. Je rencontre parfois des problèmes similaires lors du codage dans Android. Je déclare donc la variable comme étant globale et cela fonctionne pour moi.

1
Abhijay Ghildyal

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

1
user3251651

Juste une autre explication. Considérez cet 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 la sortie sera

m1 se termine

Fil t en cours

Fil t en cours

Fil t en cours

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

Maintenant, la méthode m1 () est terminée et nous affectons la variable de référence o à null. Now Outer Class Object est éligible pour GC, mais Inner Class Object existe toujours et entretient une relation (Has-A) avec l'objet Thread. Sans objet de classe Outer existant, il n'y a aucune chance que la méthode m1 () soit existante et sans la méthode m1 (), il n'y a aucune chance d'exister sa variable locale, mais si Inner Class Object utilise la variable locale de la méthode m1 (), alors tout est explicite .

Pour résoudre ce problème, nous devons créer une copie de la variable locale, puis copier dans le tas avec l'objet de classe Inner, ce que Java ne fait que pour la variable finale, car elles ne sont pas réellement variables, elles sont comme des constantes ( Tout se passe à la compilation seulement, pas à l'exécution).

0
pks

La principale préoccupation est de savoir si une variable de l'instance de classe anonyme peut être résolue au moment de l'exécution. Il n'est pas impératif de rendre une variable finale tant qu'il est garanti que la variable se trouve dans la portée d'exécution. Par exemple, consultez les deux variables _statusMessage et _statusTextView dans la méthode 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);

                }

            });
        }
    }

}
0
WaiKit Kung

Déclarez la variable en tant que statique et référencez-la dans la méthode requise à l'aide de className.variable

0
shweta

ce qui a fonctionné pour moi, c’est de définir la variable en dehors de cette fonction.

Juste avant la fonction principale, déclarez i.e.

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

Pouvez-vous créer des champs lastPrice, priceObject et price de la classe interne anonyme?

0
Greg Mattes