java - utiliza - que es una interfaz interna




No se puede referir a una variable no final dentro de una clase interna definida en un método diferente (14)

Editado: necesito cambiar los valores de varias variables, ya que se ejecutan varias veces a través de un temporizador. Necesito seguir actualizando los valores con cada iteración a través del temporizador. No puedo establecer los valores en final, ya que eso me impedirá actualizar los valores; sin embargo, recibo el error que describo en la pregunta inicial a continuación:

Anteriormente había escrito lo siguiente:

Recibo el error "no se puede referir a una variable no final dentro de una clase interna definida en un método diferente".

Esto está sucediendo para el doble precio llamado y el precio llamado priceObject. ¿Sabes por qué me sale este problema? No entiendo por qué necesito tener una declaración final. Además, si puedes ver qué es lo que estoy tratando de hacer, ¿qué tengo que hacer para solucionar este 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);
}

¿Puede hacer los lastPrice , priceObject y price de la clase interna anónima?


Acabo de escribir algo para manejar algo a lo largo de la intención del autor . Descubrí que lo mejor que podía hacer era dejar que el constructor tomara todos los objetos y luego, en tu método implementado, usara los objetos del constructor.

Sin embargo, si está escribiendo una clase de interfaz genérica, entonces tiene que pasar un Objeto, o mejor, una lista de Objetos. Esto se puede hacer con Object [] o, mejor aún, con Object ... porque es más fácil de llamar.

Ver mi ejemplo de pieza a continuación.

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

}

Por favor, consulte esta publicación sobre cierres de Java que admite esto de manera inmediata: http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

La versión 1 admite el paso de cierres no finales con 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));
        }
    });

Con las clases anónimas, en realidad está declarando una clase anidada "sin nombre". Para las clases anidadas, el compilador genera una nueva clase pública independiente con un constructor que tomará todas las variables que usa como argumentos (para las clases anidadas "nombradas", esta es siempre una instancia de la clase original / envolvente). Esto se hace porque el entorno de ejecución no tiene noción de clases anidadas, por lo que debe haber una conversión (automática) de una clase anidada a una independiente.

Toma este código por ejemplo:

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

Eso no funcionará, porque esto es lo que hace el compilador bajo el capó:

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 clase anónima original se reemplaza por alguna clase independiente que genera el compilador (el código no es exacto, pero debería darle una buena idea):

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

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

Como puede ver, la clase independiente contiene una referencia al objeto compartido, recuerde que todo en java es pasado por valor, por lo que incluso si la variable de referencia 'compartida' en EnclosingClass se modifica, la instancia a la que apunta no se modifica. , y todas las demás variables de referencia que lo señalan (como la de la clase anónima: Incluyendo $ 1), no serán conscientes de esto. Esta es la razón principal por la que el compilador le obliga a declarar estas variables 'compartidas' como finales, de modo que este tipo de comportamiento no se convierta en su código ya en ejecución.

Ahora, esto es lo que sucede cuando usa una variable de instancia dentro de una clase anónima (esto es lo que debe hacer para resolver su problema, mover su lógica a un método de "instancia" o un constructor de una clase):

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

Esto compila bien, porque el compilador modificará el código, de modo que la nueva clase generada Enclosing $ 1 mantendrá una referencia a la instancia de EnclosingClass en la que se creó una instancia (esto es solo una representación, pero debería ponerlo en marcha):

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

Así, cuando la variable de referencia 'compartida' en EnclosingClass se reasigna, y esto sucede antes de la llamada a Thread # run (), verá "otro hola" impreso dos veces, porque ahora EnclosingClass $ 1 # enclosing variable mantendrá una referencia al objeto de la clase donde se declaró, por lo que los cambios a cualquier atributo en ese objeto serán visibles para las instancias de EnclosingClass $ 1.

Para obtener más información sobre el tema, puede ver esta excelente publicación de blog (no escrita por mí): http://kevinboone.net/java_inner.html


Cuando me topo con este problema, simplemente paso los objetos a la clase interna a través del constructor. Si necesito pasar primitivas u objetos inmutables (como en este caso), se necesita una clase de envoltura.

Edición: en realidad, no uso una clase anónima en absoluto, sino una subclase adecuada:

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

Java no admite closures verdaderos, aunque usar una clase anónima como la que está usando aquí ( new TimerTask() { ... } ) parece un tipo de cierre.

Editar : vea los comentarios a continuación. La siguiente no es una explicación correcta, como señala KeeperOfTheSoul.

Por eso no funciona:

Las variables lastPrice y price son variables locales en el método main (). El objeto que cree con la clase anónima puede durar hasta después de que el método main() regrese.

Cuando se devuelve el método main() , las variables locales (como lastPrice y price ) se lastPrice de la pila, por lo que ya no existirán después de main() .

Pero el objeto de clase anónimo hace referencia a estas variables. Las cosas saldrían terriblemente mal si el objeto de clase anónimo intenta acceder a las variables después de que se hayan limpiado.

Al hacer que el lastPrice price y el price final , ya no son realmente variables, sino constantes. Entonces, el compilador simplemente puede reemplazar el uso de lastPrice y el price en la clase anónima con los valores de las constantes (en el momento de la compilación, por supuesto), y ya no tendrá el problema de acceder a las variables no existentes.

Otros lenguajes de programación que admiten cierres lo hacen al tratar esas variables especialmente, asegurándose de que no se destruyan cuando finalice el método, de modo que el cierre aún pueda acceder a las variables.

@Ankur: Podrías hacer esto:

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

La principal preocupación es si una variable dentro de la instancia de clase anónima se puede resolver en tiempo de ejecución. No es obligatorio hacer una variable final siempre que se garantice que la variable está dentro del alcance del tiempo de ejecución. Por ejemplo, vea las dos variables _statusMessage y _statusTextView dentro del método 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);

                }

            });
        }
    }

}

No puede referirse a variables no finales porque la Especificación del lenguaje Java lo dice. Desde 8.1.3:
"Cualquier variable local, parámetro de método formal o parámetro de controlador de excepción usado pero no declarado en una clase interna debe ser declarado final". Párrafo entero.
Solo puedo ver una parte de su código; según mi opinión, la modificación de las variables locales es una idea extraña. Las variables locales dejan de existir al salir de la función. Tal vez los campos estáticos de una clase serían mejores?


Para evitar efectos secundarios extraños con cierres en variables java referenciadas por un delegado anónimo, debe marcarse como final, para referirse al lastPrice precio y al precio dentro de la tarea del temporizador que deben marcarse como final.

Obviamente, esto no funcionará para usted porque desea cambiarlos, en este caso, debe considerar encapsularlos dentro de una clase.

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

ahora solo crea un nuevo Foo como final y llama a .tick desde el temporizador.

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

Porque es confuso si la variable no es definitiva, ya que los cambios a la misma no se recogerán en la clase anónima.

Simplemente haga que las variables 'precio' y 'último precio' sean finales.

- editar

Vaya, y también necesitarás no asignarlos, obviamente, en tu función. Necesitarás nuevas variables locales. De todos modos, sospecho que alguien ya te ha dado una mejor respuesta.


Si desea cambiar un valor en una llamada de método dentro de una clase anónima, ese "valor" es en realidad un Future . Entonces, si usas guayaba, puedes escribir

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

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

 return myvalue.get();

Solo se puede acceder a las variables finales de la clase contenedora cuando se usa una clase anónima. Por lo tanto, debe declarar que las variables que se están utilizando son finales (lo que no es una opción para usted, ya que está cambiando el último precio y el precio ), o no utilice una clase anónima.

De modo que sus opciones son crear una clase interna real, en la que pueda pasar las variables y utilizarlas de manera normal.

o:

Hay un hack rápido (y en mi opinión feo) para su última variable de Precio y Precio que es declararlo así

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

y en su clase anónima puede establecer el valor como este

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

Una solución que he notado no se menciona (a menos que la haya omitido, si es que me corrija) es el uso de una variable de clase. Se encontró con este problema al intentar ejecutar un nuevo hilo dentro de un método: new Thread(){ Do Something } .

Llamar a doSomething() de lo siguiente funcionará. No necesariamente tiene que declararlo final , solo necesita cambiar el alcance de la variable para que no se recopile antes de la clase interna. Esto es a menos que, por supuesto, su proceso sea enorme y cambiar el alcance pueda crear algún tipo de conflicto. No quería que mi variable fuera final, ya que de ninguna manera era una final / 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();
    }

}

use ClassName.this.variableName para hacer referencia a la variable no final


Solo una explicación más. Considere este ejemplo a continuación

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

Aquí la salida será

m1 completa

Hilo t corriendo

Hilo t corriendo

Hilo t corriendo

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

Ahora el método m1 () se completa y asignamos la variable de referencia o a nulo. El objeto Now Outer Class es elegible para GC, pero aún existe el objeto Inner Class que tiene una relación (Has-A) con el objeto Thread que se está ejecutando. Sin el objeto de clase externa existente no hay posibilidad de que exista el método m1 () y sin el método m1 () existente no hay posibilidad de que exista su variable local, pero si el objeto de clase interna utiliza la variable local del método m1 (), entonces todo se explica por sí mismo. .

Para resolver esto, tenemos que crear una copia de la variable local y luego copiarla en el montón con el objeto de clase Interna, lo que Java hace solo para la variable final porque no son realmente variables, son como constantes (todo sucede solo en tiempo de compilación no en tiempo de ejecución).







final