2011-01-19 8 views
283
  1. a solo pueden ser definitivos aquí. ¿Por qué? ¿Cómo puedo reasignar a en el método onClick() sin tenerlo como miembro privado?¿Por qué solo se puede acceder a las variables finales en clase anónima?

    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 volver al 5 * a cuando se hace clic en? Es 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 
         } 
        }); 
    } 
    
+0

No creo que las clases anónimas de Java brinden el tipo de cierre lambda que esperaría, pero alguien por favor corrígeme si me equivoco ... – Mehrdad

+4

¿Qué está tratando de lograr? El controlador de clics podría ejecutarse cuando finalice "f". –

+0

@Lambert si quiere usar un método onClick tiene que ser @Ivan final, ¿cómo puede el método f() comportarse como el método onClick() return int cuando se hace clic en –

Respuesta

408

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


Es básicamente debido a la forma en que Java maneja closures.

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 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 una manera que sea vista por el cuerpo principal del método, y viceversa.)

Como el valor ha sido copiado en la instancia de la clase interna anónima, parecería extraño si la variable pudiera ser modificada por el resto del método - podría tener un código que pareciera estar funcionando con un out- de la fecha de la variable (porque eso es efectivamente lo que estaría ocurriendo ... 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 final elimine 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 realizados en los parámetros de un método no son vistos por su interlocutor, pero los cambios realizados en los objetos referidos a por los parámetros se ven.

Si está interesado en una comparación más detallada entre cierres de Java y C#, tengo un article que va más allá. Quería centrarme en el lado de Java en esta respuesta :)

+2

Sí. Básicamente, el soporte completo de cierres podría implementarse moviendo todas las variables a las que se hace referencia en una clase especial autogenerada. –

+4

@Ivan: como C#, básicamente. Sin embargo, tiene un cierto grado de complejidad, si desea el mismo tipo de funcionalidad que C#, donde las variables de diferentes ámbitos se pueden "instanciar" diferentes números de veces. –

+0

Este constructor autogenerado, ¿por qué no puedo verlo usando reflexión? –

39

Hay un truco que permite clase anónima para la actualización de datos en el ámbito exterior.

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, necesita 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 res se actualizó

Este truco funciona bien, si clase anónima se invoca 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]); 

// ... 
+2

gracias por su respuesta. Sé todo esto y mi solución es mejor que esto. mi pregunta es "¿por qué solo final"? –

+5

La respuesta es que así es como se implementan :) –

+1

Gracias. Yo había usado el truco anterior por mi cuenta. No estaba seguro de si es una buena idea. Si Java no lo permite, puede haber una buena razón. Su respuesta aclara que mi código 'List.forEach' es seguro. – RuntimeException

8

Puede crear una variable de nivel de clase para obtener el valor devuelto. Me refiero a

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 puede obtener el valor de K y usarlo donde desee.

respuesta del por qué es:

Una instancia de la clase interna local está ligada a la clase principal, y puede acceder a las variables locales finales de su método que contiene. 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 alcance (esta es efectivamente la versión limitada de cierres de Java).

Como 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.)

+0

Mencioné que "sin mantenerlo como miembro privado" –

2

Los métodos dentro de una clase interna anonomyous se pueden invocar bien 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 alcance de las variables será diferente. Por lo tanto, para proteger tales problemas de alcance de asignación variable, debe declararlos como definitivos.

7

Bueno, en Java, una variable puede ser definitivo no sólo 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 de nivel de clase en la clase que contiene.

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

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

Y como es definitiva, Java puede con seguridad copiar en clases locales anónimas. No obtendrá ninguna 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 una int implícita llamada a en su clase anónima.

+3

Asociar "variable de nivel de clase" con 'estática'. Tal vez sea más claro si utiliza "variable de instancia" en su lugar. – eljenso

+1

bueno, utilicé el nivel de clase porque la técnica funcionaría con variables de instancia y estáticas. –

16

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

Cualquier variable local, parámetro del método formal o de los parámetros de gestión de excepciones utilizado pero no declarados en una clase interna debe declararse final. Cualquier variable local, usada pero no declarada en una clase interna debe asignarse definitivamente al cuerpo de la clase interna.

no he encontrado una razón o una explicación sobre los JLS o JVM todavía, pero lo que sí sabemos, que el compilador crea un archivo de clase independiente para cada clase interna y tiene que asegurarse de que los métodos declarado en este archivo de clase (en el nivel de código de byte) al menos tener acceso a los valores de las variables locales.

(Jon has the complete answer - guardo éste sin borrar porque uno podría interesados ​​en la regla JLS)

1
private void f(Button b, final int a[]) { 

    b.addClickHandler(new ClickHandler() { 

     @Override 
     public void onClick(ClickEvent event) { 
      a[0] = a[0] * 5; 

     } 
    }); 
} 
-1

Tal vez este truco da u 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); 
5

La razón por la cual el acceso ha sido restringido solo para 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 múltiples copias de variables locales mutables puede llevar a inc. datos persistentes. 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.

+0

No es así como se implementa en idiomas como C# que admiten esta característica. De hecho, el compilador cambia la variable de una variable local a una variable de instancia, o crea una estructura de datos adicional para estas variables que pueden superar el alcance de la clase externa. Sin embargo, no hay "copias múltiples de variables locales" – Mike76

+0

Mike76 No he echado un vistazo a la implementación de C#, pero Scala hace lo segundo que mencionaste. Creo: si un 'Int' se reasigna dentro de un cierre, cambie esa variable a una instancia de 'IntRef' (esencialmente un wrapper' Integer' mutable). Cada acceso variable se reescribe en consecuencia. – Adowrath

2

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 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 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 se ha eliminado su marco de pila, 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)

0

Como Jon tiene los detalles de implementación contestar una otra respuesta posible sería que la JVM no quiere manejar escribir en el registro que han puesto fin a su activación.

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

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

Cuestiones relacionadas