¿Por qué solo se puede acceder a las variables finales en clase anónima?

  1. a solo puede ser final aquí. ¿Por qué? ¿Cómo puedo reasignar a método onClick() sin mantenerlo como miembro privado?

     private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); } 
  2. ¿Cómo puedo devolver el 5 * a cuando hace clic? Quiero decir,

     private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); } 

Como se señala en los comentarios, parte de esto se vuelve irrelevante en Java 8, donde final puede ser implícito. Sin embargo, solo se puede usar una variable efectivamente final en una clase interna anónima o en una expresión lambda.


Básicamente se debe a la forma en que Java gestiona los cierres .

Cuando crea una instancia de una clase interna anónima, todas las variables que se usan dentro de esa clase tienen sus valores copiados a través del constructor autogenerado. Esto evita que el comstackdor tenga que autogenerar varios tipos adicionales para mantener el estado lógico de las “variables locales”, como por ejemplo el comstackdor de C # … (Cuando C # captura una variable en una función anónima, realmente captura la variable, la el cierre puede actualizar la variable de una manera que sea vista por el cuerpo principal del método, y viceversa).

Como el valor se ha copiado en la instancia de la clase interna anónima, parecería extraño si la variable pudiera ser modificada por el rest del método: podría tener un código que parecía funcionar con una variable desactualizada ( porque eso es efectivamente lo que estaría sucediendo … estarías trabajando con una copia tomada en un momento diferente). Del mismo modo, si pudiera realizar cambios dentro de la clase interna anónima, los desarrolladores podrían esperar que esos cambios sean visibles dentro del cuerpo del método adjunto.

Hacer que la variable sea definitiva elimina todas estas posibilidades, ya que el valor no se puede cambiar en absoluto, no tiene que preocuparse de si dichos cambios serán visibles. Las únicas maneras de permitir que el método y la clase interna anónima vean los cambios de los demás es usar un tipo mutable de alguna descripción. Esta podría ser la propia clase adjunta, una matriz, un tipo de envoltura mutable … algo así. Básicamente, es un poco como comunicarse entre un método y otro: los cambios hechos a los parámetros de un método no son vistos por su interlocutor, pero se ven los cambios hechos a los objetos referidos por los parámetros.

Si está interesado en una comparación más detallada entre cierres de Java y C #, tengo un artículo que lo profundiza. Quería centrarme en el lado de Java en esta respuesta 🙂

Hay un truco que permite que la clase anónima actualice datos en el ámbito externo.

 private void f(Button b, final int a) { final int[] res = new int[1]; b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { res[0] = a * 5; } }); // But at this point handler is most likely not executed yet! // How should we now res[0] is ready? } 

Sin embargo, este truco no es muy bueno debido a los problemas de sincronización. Si se invoca el controlador más tarde, debe: 1) sincronizar el acceso a res si se invocó el controlador desde el hilo diferente 2) necesitar tener algún tipo de indicador o indicación de que se actualizó res

Este truco funciona bien, sin embargo, si se invoca la clase anónima en el mismo hilo de inmediato. Me gusta:

 // ... final int[] res = new int[1]; Runnable r = new Runnable() { public void run() { res[0] = 123; } }; r.run(); System.out.println(res[0]); // ... 

Una clase anónima es una clase interna y la regla estricta se aplica a las clases internas (JLS 8.1.3) :

Cualquier variable local, parámetro de método formal o parámetro de controlador de excepción utilizado pero no declarado en una clase interna debe declararse final . Cualquier variable local, usada pero no declarada en una clase interna debe ser definitivamente asignada antes que el cuerpo de la clase interna .

Todavía no he encontrado una razón o una explicación sobre jls o jvms, pero sí sabemos que el comstackdor crea un archivo de clase separado para cada clase interna y debe asegurarse de que los métodos declarados en este archivo de clase ( en el nivel de código de bytes) al menos tener acceso a los valores de las variables locales.

( Jon tiene la respuesta completa : la dejo sin recuperar porque podría interesarle la regla de JLS)

Puede crear una variable de nivel de clase para obtener el valor devuelto. quiero decir

 class A { int k = 0; private void f(Button b, int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { k = a * 5; } }); } 

ahora puedes obtener el valor de K y usarlo donde quieras.

Respuesta de tu por qué es:

Una instancia de clase interna local está vinculada a la clase Principal y puede acceder a las variables locales finales de su método contenedor. Cuando la instancia utiliza un local final de su método contenedor, la variable conserva el valor que tenía en el momento de la creación de la instancia, incluso si la variable se ha salido del scope (esta es efectivamente la versión limitada de cierres de Java).

Debido a que una clase interna local no es miembro de una clase o paquete, no se declara con un nivel de acceso. (Tenga en claro, sin embargo, que sus propios miembros tienen niveles de acceso como en una clase normal).

Bueno, en Java, una variable puede ser definitiva no solo como un parámetro, sino como un campo de nivel de clase, como

 public class Test { public final int a = 3; 

o como una variable local, como

 public static void main(String[] args) { final int a = 3; 

Si desea acceder y modificar una variable de una clase anónima, es posible que desee convertir la variable en una variable de nivel de clase en la clase adjunta .

 public class Test { public int a; public void doSomething() { Runnable runnable = new Runnable() { public void run() { System.out.println(a); a = a+1; } }; } } 

No puede tener una variable como final y darle un nuevo valor. final significa exactamente eso: el valor es inmutable y final.

Y dado que es definitivo, Java puede copiarlo de forma segura en clases anónimas locales. No obtendrás ninguna referencia al int (especialmente porque no puedes tener referencias a primitivas como int en Java, solo referencias a Objects ).

Simplemente copia el valor de a en una int implícita llamada a en su clase anónima.

La razón por la cual el acceso se ha restringido solo a las variables finales locales es que si todas las variables locales se hicieran accesibles, entonces primero se requeriría que se copiaran a una sección separada donde las clases internas pueden tener acceso a ellas y mantener copias múltiples de variables locales mutables pueden conducir a datos inconsistentes. Mientras que las variables finales son inmutables y, por lo tanto, cualquier cantidad de copias para ellos no tendrá ningún impacto en la coherencia de los datos.

Los métodos dentro de una clase interna anonomosa pueden invocarse mucho después de que el hilo que generó su terminación. En su ejemplo, la clase interna se invocará en el hilo de envío del evento y no en el mismo hilo que el que lo creó. Por lo tanto, el scope de las variables será diferente. Por lo tanto, para proteger tales problemas de scope de asignación variable, debe declararlos como definitivos.

Cuando se define una clase interna anónima dentro del cuerpo de un método, todas las variables declaradas finales en el scope de ese método son accesibles desde dentro de la clase interna. Para valores escalares, una vez que se ha asignado, el valor de la variable final no puede cambiar. Para valores de objeto, la referencia no puede cambiar. Esto permite que el comstackdor de Java “capture” el valor de la variable en tiempo de ejecución y almacene una copia como un campo en la clase interna. Una vez que el método externo ha terminado y se ha eliminado su marco de stack, la variable original se ha ido, pero la copia privada de la clase interna persiste en la propia memoria de la clase.

( http://en.wikipedia.org/wiki/Final_%28Java%29 )

 private void f(Button b, final int a[]) { b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { a[0] = a[0] * 5; } }); } 

Como Jon tiene los detalles de implementación, otra posible respuesta sería que la JVM no quiere manejar la escritura en el registro que ha terminado su activación.

Considere el caso de uso donde sus lambdas en lugar de aplicar, se almacenan en algún lugar y se ejecutan más tarde.

Recuerdo que en Smalltalk tendrías una tienda ilegal levantada cuando hagas tal modificación.

Prueba este código,

Crear lista de matrices y poner valor dentro de eso y devolverlo:

 private ArrayList f(Button b, final int a) { final ArrayList al = new ArrayList(); b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; al.add(b); } }); return al; } 

Tal vez este truco te dé una idea

 Boolean var= new anonymousClass(){ private String myVar; //String for example @Overriden public Boolean method(int i){ //use myVar and i } public String setVar(String var){myVar=var; return this;} //Returns self instane }.setVar("Hello").method(3);