Impossibile fare riferimento a una variabile non finale all'interno di una classe interna definita in un metodo diverso


247

Modificato: ho bisogno di cambiare i valori di più variabili mentre corrono più volte attraverso un timer. Devo continuare ad aggiornare i valori con ogni iterazione tramite il timer. Non riesco a impostare i valori su final in quanto ciò mi impedirà di aggiornare i valori, tuttavia sto ricevendo l'errore che descrivo nella domanda iniziale di seguito:

In precedenza avevo scritto ciò che è sotto:

Ricevo l'errore "non posso fare riferimento a una variabile non finale all'interno di una classe interna definita in un metodo diverso".

Questo sta accadendo per il doppio chiamato price e il prezzo chiamato priceObject. Sai perché ho questo problema. Non capisco perché devo avere una dichiarazione finale. Inoltre, se riesci a vedere cosa sto cercando di fare, cosa devo fare per aggirare questo problema.

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

Quello che sto chiedendo è, come posso ottenere una variabile in un timer che posso aggiornare continuamente.
Ankur,

1
@Ankur: la risposta semplice è "No". Ma puoi ottenere l'effetto desiderato usando una classe interna; vedi la risposta di @ petercardona.
Stephen C,

Risposte:


197

Java non supporta le chiusure vere , anche se l'utilizzo di una classe anonima come quella utilizzata qui ( new TimerTask() { ... }) sembra una specie di chiusura.

modifica - Vedi i commenti qui sotto - la seguente non è una spiegazione corretta, come sottolinea KeeperOfTheSoul.

Ecco perché non funziona:

Le variabili lastPricee il prezzo sono variabili locali nel metodo main (). L'oggetto creato con la classe anonima potrebbe durare fino a quando non verrà main()restituito il metodo.

Quando il main()metodo ritorna, le variabili locali (come lastPricee price) verranno ripulite dallo stack, quindi non esisteranno più dopo i main()ritorni.

Ma l'oggetto classe anonimo fa riferimento a queste variabili. Le cose andrebbero terribilmente male se l'oggetto di classe anonimo provasse ad accedere alle variabili dopo che sono state ripulite.

Creando lastPricee price final, non sono più realmente variabili, ma costanti. Il compilatore può quindi sostituire l'uso di lastPricee pricenella classe anonima con i valori delle costanti (in fase di compilazione, ovviamente) e non avrai più il problema di accedere a variabili inesistenti.

Altri linguaggi di programmazione che supportano le chiusure lo fanno trattando quelle variabili in modo speciale, assicurandosi che non vengano distrutte alla fine del metodo, in modo che la chiusura possa ancora accedere alle variabili.

@Ankur: potresti farlo:

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

34
Non del tutto vero, Java genera acquisizioni per le variabili in questione per catturare i loro valori di runtime, è solo che volevano evitare uno strano effetto collaterale possibile in .Net dove da te acquisisci il valore nel delegato, modifica il valore nel metodo esterno e ora il delegato vede il nuovo valore, vedi stackoverflow.com/questions/271440/c-captured-variable-in-loop per l'esempio C # di questo comportamento che Java intende evitare.
Chris Chilvers,

14
Questo non è uno "strano effetto collaterale", è il comportamento normale che le persone si aspettano - e che Java non può fornire perché non genera acquisizioni. Come soluzione alternativa, le variabili locali utilizzate in una classe anonima devono essere definitive.
Michael Borgwardt,

12
Jesper, probabilmente dovresti modificare le parti errate delle tue risposte piuttosto che semplicemente avere un messaggio che dice che quanto sopra non è corretto.
James McMahon,

19
Java, infatti, non supporta le chiusure. Le lingue che supportano le chiusure lo fanno archiviando l'intero ambiente locale (ovvero l'insieme delle variabili locali definite nel frame dello stack corrente) come oggetto heap. Java non ha supporto per questo (i progettisti del linguaggio volevano implementarlo ma rimanevano senza tempo), quindi come soluzione alternativa, ogni volta che viene creata un'istanza di una classe locale, i valori di tutte le variabili locali a cui fa riferimento vengono copiati nell'heap . Tuttavia, la JVM non può quindi sincronizzare i valori con le variabili locali, motivo per cui devono essere definitivi.
Taymon

64
Questa risposta è completamente confusa ora che non c'è nessuno che si chiami "KeeperOfTheSoul". La risposta dovrebbe essere rivista.
Adam Parkin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.