qué - tipos de public en java
¿Por qué solo las variables finales son accesibles en clase anónima? (13)
Bueno, en Java, una variable puede ser final 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 que la variable sea 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 puedes tener una variable como final y darle un nuevo valor. final
significa justamente eso: el valor es inmutable y final.
Y como es definitivo, Java puede copiarlo de forma segura en clases anónimas locales. No está obteniendo alguna referencia al int (especialmente porque no puede tener referencias a primitivas como int en Java, solo referencias a Objetos ).
Simplemente copia el valor de a en un int implícito llamado a en su clase anónima.
a
solo puede ser final aquí. ¿Por qué? ¿Cómo puedo reasignara
método enonClick()
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; } }); }
¿Cómo puedo devolver el
5 * a
cuando hice 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 Jon tiene la respuesta a los detalles de la implementación, otra respuesta posible sería que la JVM no quiera manejar la escritura en el registro que haya finalizado su activación.
Considere el caso de uso donde sus lambdas en lugar de aplicarse, se almacenan en algún lugar y se ejecutan más tarde.
Recuerdo que en Smalltalk obtendrías una tienda ilegal cuando realices dicha modificación.
Como se señaló en los comentarios, algo de esto se vuelve irrelevante en Java 8, donde final
puede ser implícito. Sin embargo, solo una variable final efectiva puede usarse en una clase interna anónima o en una expresión lambda.
Básicamente se debe a la forma en que Java gestiona los closures .
Cuando creas 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 compilador tenga que autogenerar varios tipos adicionales para mantener el estado lógico de las "variables locales", como por ejemplo el compilador 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 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 el resto del método modificara la variable; podría tener un código que parecía estar funcionando con una variable desactualizada ( porque eso es efectivamente lo que estaría sucediendo ... estaría 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 de encierro.
La finalización de la variable elimina todas estas posibilidades: como el valor no se puede cambiar en absoluto, no tiene que preocuparse por si dichos cambios serán visibles. Las únicas formas de permitir que el método y la clase interna anónima se vean los cambios de los demás es usar un tipo mutable de alguna descripción. Esta podría ser la clase envolvente en sí misma, una matriz, un tipo de envoltura mutable ... algo así. Básicamente, es un poco como comunicarse entre un método y otro: los llamadores no ven los cambios realizados en los parámetros de un método, sino los cambios realizados en los objetos a los que hacen referencia los parámetros.
Si está interesado en una comparación más detallada entre los cierres de Java y C #, tengo un article que lo profundiza. Quería enfocarme en el lado de Java en esta respuesta :)
Cuando se define una clase interna anónima dentro del cuerpo de un método, todas las variables declaradas finales en el alcance de ese método son accesibles desde la clase interna. Para los 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 compilador 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 su marco de pila se ha eliminado, la variable original desaparece pero la copia privada de la clase interna persiste en la propia memoria de la clase.
Hay un truco que permite a la clase anónima actualizar 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 la resolución si se invocó el controlador desde el subproceso diferente 2) debe tener algún tipo de indicador o indicación de que la actualización se actualizó
Este truco funciona bien, sin embargo, si la clase anónima se invoca en el mismo hilo inmediatamente. 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]);
// ...
La razón por la que el acceso se ha restringido solo a las variables finales locales es que, si todas las variables locales se hicieran accesibles, primero tendrían que copiarse en una sección separada donde las clases internas puedan tener acceso a ellas y mantener copias múltiples de Las variables locales mutables pueden llevar a datos inconsistentes. Mientras que las variables finales son inmutables y, por lo tanto, cualquier número de copias no tendrá ningún impacto en la consistencia de los datos.
Los métodos dentro de una clase interna anónima pueden invocarse mucho después de que el hilo que la generó haya terminado. En su ejemplo, la clase interna se invocará en el subproceso de distribución de eventos y no en el mismo subproceso que lo creó. Por lo tanto, el alcance de las variables será diferente. Por lo tanto, para proteger estos problemas de alcance de asignación variable, debe declararlos definitivos.
Para comprender la razón de esta restricción, considere el siguiente programa:
public class Program {
interface Interface {
public void printInteger();
}
static Interface interfaceInstance = null;
static void initialize(int val) {
class Class implements interface {
@Override
public void printInteger() {
System.out.println(val);
}
}
interfaceInstance = new Class();
}
public static void main(String[] args) {
initialize(12345);
interfaceInstance.printInteger();
}
}
La interfaceInstance permanece en la memoria después de que el método de inicialización regresa, pero el parámetro val no lo hace. La JVM no puede acceder a una variable local fuera de su alcance, por lo que Java hace que la llamada subsiguiente a printInteger funcione copiando el valor de val a un campo implícito del mismo nombre dentro de interfaceInstance . Se dice que la interfaceInstance ha capturado el valor del parámetro local. Si el parámetro no fuera definitivo (o efectivamente final), su valor podría cambiar y perder la sincronización con el valor capturado, lo que podría provocar un comportamiento no intuitivo.
Prueba este código,
Cree una lista de matrices y ponga valor dentro de ella y devuélvala:
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;
}
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.
La respuesta de tu porqué 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 de contención. Cuando la instancia utiliza un final local de su método de contención, la variable conserva el valor que tenía en el momento de la creación de la instancia, incluso si la variable ha salido del alcance (esto es efectivamente la versión cruda y limitada de los 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. (Sea claro, sin embargo, que sus propios miembros tienen niveles de acceso como en una clase normal).
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);
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 usado pero no declarado en una clase interna debe ser declarado final . Cualquier variable local, utilizada pero no declarada en una clase interna debe asignarse definitivamente antes del cuerpo de la clase interna .
Todavía no he encontrado una razón o una explicación en jls o jvms, pero sí sabemos que el compilador crea un archivo de clase separado para cada clase interna y tiene que asegurarse de que los métodos declarados en este archivo de clase ( en el nivel de código de byte) al menos tienen acceso a los valores de las variables locales.
( Jon tiene la respuesta completa : no la borro porque estoy interesada en la regla JLS)
private void f(Button b, final int a[]) {
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
a[0] = a[0] * 5;
}
});
}