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

247

Modifié: j'ai besoin de modifier les valeurs de plusieurs variables car elles s'exécutent plusieurs fois par 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 précédemment écrit ce qui est ci-dessous:

J'obtiens l'erreur "ne peut pas faire référence à une variable non finale à l'intérieur d'une classe interne définie dans une méthode différente".

Cela se produit pour le double appelé prix 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. De plus, si vous pouvez voir ce que j'essaie 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);
}
Ankur
la source
Ce que je demande, c'est comment obtenir une variable dans une minuterie que je peux mettre à jour en continu.
Ankur
1
@Ankur: la réponse simple est "Non". Mais vous pouvez obtenir l'effet souhaité en utilisant une classe interne; voir la réponse de @ petercardona.
Stephen C

Réponses:

197

Java ne prend pas en charge les vraies fermetures , même si l'utilisation d'une classe anonyme comme celle que vous utilisez ici ( new TimerTask() { ... }) ressemble à une sorte de fermeture.

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

C'est pourquoi cela ne fonctionne pas:

Les variables lastPriceet le prix 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 main()méthode.

Lorsque la main()méthode revient, les variables locales (telles que lastPriceet price) seront nettoyées de la pile, elles n'existeront donc plus après les main()retours.

Mais l'objet de classe anonyme fait référence à ces variables. Les choses iraient terriblement mal si l'objet de classe anonyme essayait d'accéder aux variables après leur nettoyage.

En faisant lastPriceet price final, ce ne sont plus vraiment des variables, mais des constantes. Le compilateur peut alors simplement remplacer l'utilisation de lastPriceet pricedans la classe anonyme par les valeurs des constantes (au moment de la compilation, bien sûr), et vous n'aurez plus le problème d'accéder à des variables inexistantes.

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

@Ankur: Vous pouvez le faire:

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);      
}
Jesper
la source
34
Pas tout à fait vrai, Java génère des captures pour les variables en question pour capturer leurs valeurs d'exécution, c'est juste qu'ils voulaient éviter un effet secondaire étrange qui est possible dans .Net où en capturant la valeur dans le délégué, changez le valeur dans la méthode externe, et maintenant le délégué voit la nouvelle valeur voir, stackoverflow.com/questions/271440/c-captured-variable-in-loop pour l'exemple C # de ce comportement que Java vise à éviter.
Chris Chilvers
14
Ce n'est pas un "effet secondaire étrange", c'est le comportement normal auquel les gens s'attendent - et que Java ne peut pas offrir car il ne génère pas de captures. Comme solution de contournement, les variables locales utilisées dans une classe anonyme doivent être finales.
Michael Borgwardt
12
Jesper, vous devriez probablement éditer les parties incorrectes de vos réponses plutôt que de simplement avoir un message disant que ce qui précède est incorrect.
James McMahon
19
Java ne prend pas en charge les fermetures. Les langues qui prennent en charge les fermetures le font en stockant tout l'environnement local (c'est-à-dire l'ensemble des variables locales définies dans le cadre de pile actuel) en tant qu'objet de segment de mémoire. Java ne prend pas en charge cela (les concepteurs de langage voulaient l'implémenter mais manquaient de temps), donc comme solution de contournement, chaque fois qu'une classe locale est instanciée, les valeurs de toutes les variables locales auxquelles elle se réfère sont copiées sur le tas . Cependant, la JVM ne peut pas alors synchroniser les valeurs avec les variables locales, c'est pourquoi elles doivent être définitives.
Taymon
64
Cette réponse est complètement déroutante maintenant qu'il n'y a personne nommé "KeeperOfTheSoul" qui l'a commenté. La réponse devrait être révisée.
Adam Parkin
32

Pour éviter des effets secondaires étranges avec des fermetures dans les variables java référencées par un délégué anonyme, lastPriceelles doivent être marquées comme finales.

Cela ne fonctionnera évidemment pas pour vous parce que vous souhaitez les changer, 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;
    }
}

il suffit maintenant de créer un nouveau Foo en tant que final et d'appeler .tick depuis 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);
}
Chris Chilvers
la source
1
ou vous pouvez simplement conduire Foo à implémenter Runnable ..?
vidstige
18

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

Vos options sont donc 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 dernière variable de prix et de prix qui est de le déclarer ainsi

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];
Robin
la source
14

De bonnes explications pour lesquelles vous ne pouvez pas faire ce que vous essayez de faire déjà fournies. Comme 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 );
    }
}

Il semble que vous pourriez probablement faire une meilleure conception que cela, mais l'idée est que vous pouvez regrouper les variables mises à jour dans une référence de classe qui ne change pas.

Peter Cardona
la source
11

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 prendra toutes les variables qu'il utilise comme arguments (pour les classes imbriquées "nommées", il s'agit toujours d'une instance de la classe d'origine / englobante). Cela est dû au fait 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 vers 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, 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 d'origine 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 voir, la classe autonome contient une référence à l'objet partagé, rappelez-vous que tout en java est une valeur de passage, donc même si la variable de référence 'shared' dans EnclosingClass est modifiée, l'instance vers laquelle elle pointe n'est pas modifiée , et toutes les autres variables de référence pointant vers lui (comme celle de la classe anonyme: Enclosing $ 1), n'en seront pas conscientes. C'est la raison principale pour laquelle le compilateur vous oblige à déclarer ces variables «partagées» comme finales, afin que ce type de comportement ne soit pas intégré dans votre code déjà en cours d'exécution.

Maintenant, c'est ce qui se passe lorsque vous utilisez une variable d'instance à l'intérieur d'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 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);
    }
}

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 (ce n'est qu'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);
    }
}

Comme ceci, lorsque la variable de référence «partagée» dans EnclosingClass est réaffectée, et cela se produit avant l'appel à Thread # run (), vous verrez «autre bonjour» imprimé deux fois, parce que maintenant la variable entourante EnclosingClass $ 1 # gardera une référence à l'objet de la classe où il a été déclaré, les modifications apportées à tout attribut de cet objet seront donc visibles par les instances de EnclosingClass $ 1.

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

emerino
la source
Que faire si la variable locale «partagée» est un objet mutable? Selon votre explication, déclarer «final» n'aidera pas non plus, non?
sactiw
Déclarer "partagé" comme final vous permettra de modifier l'état de l'objet les références de variables finales, mais pour cet exemple particulier qui ne fonctionnera pas car vous ne pourrez pas changer la valeur de la variable "partagée" (qui est ce que l'OP voulait), vous pourrez l'utiliser dans des classes anonymes, mais sa valeur ne changera pas (car elle est déclarée finale). Il est important de noter la différence entre les variables et les valeurs réelles qu'elles contiennent (qui peuvent être des primitives ou des références à des objets en tas).
emerino
>>> mais sa valeur ne changera pas Je suppose que vous manquez le point c'est-à-dire que si la variable de référence finale pointe vers un objet mutable, elle peut toujours être mise à jour, cependant, la classe anonyme crée la copie superficielle ainsi les changements sont reflétés dans l'anonyme classe. En d'autres termes, les états sont synchronisés, ce qui est souhaité ici. Ici, OP a besoin d'une capacité pour modifier la variable partagée (le type primitif) et pour atteindre cet objectif, OP devra envelopper la valeur sous un objet mutable et partager cet objet mutable.
sactiw
1
Bien sûr, l'OP peut encapsuler la valeur requise sous un objet mutable, déclarer la variable comme finale et l'utiliser à la place. Cependant, il peut éviter d'utiliser un objet supplémentaire en déclarant la variable comme attribut de la classe actuelle (comme indiqué et expliqué dans la réponse). Forcer des objets mutables (comme utiliser des tableaux juste pour pouvoir modifier la valeur d'une variable partagée) n'est pas une bonne idée.
emerino
7

Lorsque je tombe sur ce problème, je passe simplement les objets à la classe interne via le 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);
    }
Buhb
la source
2

Vous ne pouvez pas faire référence à des variables non finales car la spécification du langage Java le dit. De 8.1.3:
"Toute variable locale, paramètre de méthode formelle ou paramètre de gestionnaire d'exception utilisé mais non déclaré dans une classe interne doit être déclaré final." Paragraphe entier.
Je ne peux voir qu'une partie de votre code - selon moi, la planification de la 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 seraient mieux?

Tadeusz Kopec
la source
2

Je viens d'écrire quelque chose pour gérer quelque chose selon l' intention des auteurs . J'ai trouvé que la meilleure chose à faire était de laisser le constructeur prendre tous les objets et ensuite dans votre méthode implémentée utiliser ces objets 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 être fait par Object [] ou encore mieux, Object ... car il est plus facile d'appeler.

Voir mon exemple de pièce juste en 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 cet article sur les fermetures Java qui prend en charge cela hors 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 définitives avec 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));
        }
    });
mmm
la source
2

Si vous souhaitez modifier une valeur dans un appel de méthode au sein d'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();
Earth Engine
la source
2

Une solution que j'ai remarquée n'est pas mentionnée (sauf si je l'ai manquée, si je la corrige 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 n'avez pas nécessairement à le déclarer, il finalsuffit de changer la portée de la variable pour qu'elle ne soit pas collectée avant la classe interne. C'est à moins bien sûr que votre processus soit énorme et que le changement de 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();
    }

}
James
la source
1

Si la variable requise pour être finale ne peut pas l'être, vous pouvez attribuer la valeur de la variable à une autre variable et la rendre finale pour pouvoir l'utiliser à la place.

Thorbjørn Ravn Andersen
la source
1

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

user3251651
la source
1

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 parfois face à des problèmes similaires lors du codage dans Android, donc je déclare la variable comme globale et cela fonctionne pour moi.

Abhijay Ghildyal
la source
Cela ne répond pas vraiment à la question ... C'est pourquoi vous obtenez un vote négatif.
Stuart Siegler
0

Pouvez - vous faire lastPrice, priceObjectet les pricechamps de la classe interne anonyme?

Greg Mattes
la source
0

La principale préoccupation est de savoir si une variable à l'intérieur de l'instance de classe anonyme peut être résolue au moment de l'exécution. Il n'est pas indispensable 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);

                }

            });
        }
    }

}
WaiKit Kung
la source
0

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

Juste avant la fonction principale, déclarer ie

Double price;
public static void main(String []args(){
--------
--------
}
punkck
la source
Cela ne fonctionnera pas, vous déclarez une variable d'instance, pour l'utiliser, vous devrez créer une instance à l'intérieur de votre méthode principale. Vous devez être plus précis ou simplement ajouter le modificateur statique à la variable «prix».
emerino
0

Déclarez la variable comme statique et référencez-la dans la méthode requise en utilisant className.variable

shweta
la source
Non-static parameter cannot be referenced from a static context
stephen
Les variables locales et les paramètres de méthode @Shweta ne peuvent pas être déclarés `` statiques '', de plus, il s'agit de la façon dont il a été implémenté pour permettre aux classes dans les méthodes (classes anonymes locales) de continuer à accéder aux variables locales et aux paramètres de méthode même après la méthode est retourné, c'est-à-dire qu'il fait leurs copies «finales» et les utilise comme variables d'instance.
sactiw
0

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 termine

Thread t running

Thread t running

Thread t running

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

Maintenant, la méthode m1 () se termine et nous affectons la variable de référence o à null, maintenant l'objet de classe externe est éligible pour GC mais l'objet de classe interne existe toujours qui a une relation (Has-A) avec l'objet Thread en cours d'exécution. Sans objet de classe externe existant, il n'y a aucune chance que la méthode m1 () existe et sans méthode m1 (), il n'y a aucune chance d'exister sa variable locale, mais si l'objet de classe interne utilise la variable locale de la méthode m1 (), alors tout s'explique de lui-même. .

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 fait pour la seule variable finale, car ils ne sont pas réellement variables, ils sont comme des constantes (tout se passe au moment de la compilation uniquement pas à l'exécution).

pks
la source
-1

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

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

pour C #, la solution est d'autoriser les effets secondaires et la capture par référence est la seule option.

pour C ++ 11, la solution est de permettre au programmeur de prendre la décision. Ils peuvent choisir de capturer par valeur ou par référence. En cas de capture par valeur, aucun effet secondaire ne se produirait car la variable référencée est en fait différente. Si la capture par référence, des effets secondaires peuvent se produire, mais le programmeur doit s'en rendre compte.

Earth Engine
la source
-2

Parce que c'est déroutant si la variable n'est pas définitive, car les modifications ne seront pas récupérées dans la classe anonyme.

Rendez les variables 'prix' et 'lastPrice' finales.

-- Éditer

Oups, et vous devrez également ne pas leur affecter, évidemment, dans votre fonction. Vous aurez besoin de nouvelles variables locales. Quoi qu'il en soit, je soupçonne que quelqu'un vous a déjà donné une meilleure réponse.

Noon Silk
la source
2
ce n'est pas seulement déroutant - c'est carrément incorrect, donc le compilateur ne le permet pas.
Chii
Mais alors, comment puis-je changer les valeurs quand j'en ai besoin?
Ankur
Pas seulement parce que c'est déroutant; c'est parce que Java ne prend pas en charge les fermetures. Voir ma réponse ci-dessous. @Ankur: Vous pouvez faire des variables des variables membres de l'objet de classe anonyme au lieu de variables locales dans main ().
Jesper
Il les modifie, donc ils ne peuvent pas être définitifs.
Robin
Si price et lastPrice étaient définitifs, les affectations à ces derniers ne seraient pas compilées.
Greg Mattes