¿Cómo pasar parámetros a la clase anónima?

146

¿Es posible pasar parámetros o acceder a parámetros externos a una clase anónima? Por ejemplo:

int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // How would one access myVariable here?
    }
});

¿Hay alguna forma para que el oyente acceda a myVariable o se le pase myVariable sin crear el oyente como una clase con nombre real?

Luis
fuente
77
Puede hacer referencia a finalvariables locales desde el método de inclusión.
Tom Hawtin - tackline
Me gusta el aspecto de la sugerencia de Adam Mmlodzinski de definir un método privado que inicialice las instancias privadas myVariable y que se pueda llamar en la llave de cierre debido a la devolución this.
dlamblin
Esta pregunta tiene algunos objetivos compartidos de: stackoverflow.com/questions/362424/…
Alastair McCormack
También puede usar las variables de clase global desde dentro de la clase anónima. Quizás no esté muy limpio, pero puede hacer el trabajo.
Jori

Respuestas:

78

Técnicamente, no, porque las clases anónimas no pueden tener constructores.

Sin embargo, las clases pueden hacer referencia a variables que contienen ámbitos. Para una clase anónima, estas pueden ser variables de instancia de las clases que lo contienen o variables locales que están marcadas como finales.

editar : como señaló Peter, también puede pasar parámetros al constructor de la superclase de la clase anónima.

Matthew Willis
fuente
21
Un uso de clase anónimo utiliza los constructores de su padre. por ejemplonew ArrayList(10) { }
Peter Lawrey
Buen punto. Esa sería otra forma de pasar un parámetro a una clase anónima, aunque es probable que no tenga control sobre ese parámetro.
Matthew Willis
las clases anónimas no necesitan constructores
newacct
44
Las clases anónimas pueden tener inicializadores de instancia, que pueden funcionar como constructores sin parámetros en clases anónimas. Estos se ejecutan en el mismo orden que las asignaciones de campo, es decir, después super()y antes del resto del constructor real. new someclass(){ fields; {initializer} fields; methods(){} }. Es algo así como un inicializador estático pero sin la palabra clave estática. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus el
Vea este stackoverflow.com/a/3045185/1737819 que dice cómo implementar sin constructor.
Desarrollador Marius Žilėnas
336

Sí, agregando un método de inicializador que devuelve 'this' e inmediatamente llamando a ese método:

int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    private int anonVar;
    public void actionPerformed(ActionEvent e) {
        // How would one access myVariable here?
        // It's now here:
        System.out.println("Initialized with value: " + anonVar);
    }
    private ActionListener init(int var){
        anonVar = var;
        return this;
    }
}.init(myVariable)  );

No se necesita declaración 'final'.

Adam Mlodzinski
fuente
44
wow ... genial! Estoy tan cansado de crear un finalobjeto de referencia para poder obtener información en mis clases anónimas. ¡Gracias por compartir!
Matt Klein
77
¿Por qué la init()función tiene que volver this? Realmente no entiendo la sintaxis.
Jori
11
porque su myButton.addActionListener (...) espera un objeto ActionListener como un objeto que se devuelve cuando llama a su método.
1
Supongo ... eso me parece bastante feo, aunque funciona. Creo que la mayoría de las veces puedo simplemente permitir que las variables necesarias y los parámetros de la función sean finales y los haga referencia directamente desde la clase interna, ya que generalmente solo se están leyendo.
Thomas
2
Más simple: private int anonVar = myVariable;
Anm
29

si. Puede capturar variables, visibles para la clase interna. la única limitación es que tiene que ser final

aav
fuente
Las variables de instancia a las que se hace referencia desde una clase anónima no tienen que ser afaik finales.
Matthew Willis
8
Se hace referencia a las variables de instancia a través de lo thiscual es final.
Peter Lawrey
¿Qué sucede si no quiero que se cambie la variable final? No puedo encontrar ninguna alternativa. Esto puede influir en el parámetro de origen que está diseñado para ser final.
Alston
20

Me gusta esto:

final int myVariable = 1;

myButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Now you can access it alright.
    }
});
adarshr
fuente
14

Esto hará la magia

int myVariable = 1;

myButton.addActionListener(new ActionListener() {

    int myVariable;

    public void actionPerformed(ActionEvent e) {
        // myVariable ...
    }

    public ActionListener setParams(int myVariable) {

        this.myVariable = myVariable;

        return this;
    }
}.setParams(myVariable));

fuente
8

Como se muestra en http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class , puede agregar un inicializador de instancia. Es un bloque que no tiene nombre y se ejecuta primero (como un constructor).

Parece que también se discuten en ¿Por qué inicializadores de instancias de Java? y ¿Cómo es un inicializador de instancia diferente de un constructor? discute las diferencias de los constructores.

Rob Russell
fuente
Esto no resuelve la pregunta que se hace. Aún tendrá el problema de acceder a las variables locales, por lo que deberá utilizar la solución de Adam Mlodzinski o adarshr
Matt Klein
1
@MattKlein Para mí, parece que lo resuelve. Es lo mismo en realidad, y menos detallado.
haelix
1
La pregunta quería saber cómo pasar parámetros a la clase, como lo haría con un constructor que requiere parámetros. El enlace (que debería haberse resumido aquí) solo muestra cómo tener un inicializador de instancia sin parámetros, que no responde la pregunta. Esta técnica podría usarse con finalvariables como se describe en aav, pero esa información no se proporcionó en esta respuesta. Con mucho, la mejor respuesta es la dada por Adam Mlodzinksi (ahora uso este patrón exclusivamente, ¡no más finales!). Respaldo mi comentario de que esto no responde la pregunta formulada.
Matt Klein el
7

Mi solución es usar un método que devuelva la clase anónima implementada. Se pueden pasar argumentos regulares al método y están disponibles dentro de la clase anónima.

Por ejemplo: (de algún código GWT para manejar un cambio de cuadro de texto):

/* Regular method. Returns the required interface/abstract/class
   Arguments are defined as final */
private ChangeHandler newNameChangeHandler(final String axisId, final Logger logger) {

    // Return a new anonymous class
    return new ChangeHandler() {
        public void onChange(ChangeEvent event) {
            // Access method scope variables           
            logger.fine(axisId)
        }
     };
}

Para este ejemplo, se haría referencia al nuevo método de clase anónimo con:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

O , utilizando los requisitos del OP:

private ActionListener newActionListener(final int aVariable) {
    return new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Your variable is: " + aVariable);
        }
    };
}
...
int myVariable = 1;
newActionListener(myVariable);
Alastair McCormack
fuente
Esto es bueno, restringe la clase anónima a unas pocas variables fáciles de identificar y elimina las abominaciones de tener que hacer que algunas variables sean finales.
Variable miserable
3

Otras personas ya han respondido que las clases anónimas solo pueden acceder a las variables finales. Pero dejan abierta la pregunta de cómo mantener la variable original no final. Adam Mlodzinski dio una solución pero está bastante hinchado. Hay una solución mucho más simple para el problema:

Si no desea myVariableser definitivo, debe envolverlo en un nuevo ámbito donde no importa, si es definitivo.

int myVariable = 1;

{
    final int anonVar = myVariable;

    myButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // How would one access myVariable here?
            // Use anonVar instead of myVariable
        }
    });
}

Adam Mlodzinski no hace nada más en su respuesta, pero con mucho más código.

ceving
fuente
Esto todavía funciona sin el alcance adicional. Es efectivamente lo mismo que las otras respuestas usando final.
Adam Mlodzinski
@AdamMlodzinski No, es efectivamente lo mismo que su respuesta, porque introduce una nueva variable con el valor de la variable original en un ámbito privado.
ceving
No es efectivamente lo mismo. En su caso, su clase interna no puede realizar cambios en anonVar, por lo tanto, el efecto es diferente. Si su clase interna tiene que, por ejemplo, mantener algún estado, su código tendría que usar algún tipo de Objeto con un setter en lugar de un primitivo.
Adam Mlodzinski el
@AdamMlodzinski Esa no era la pregunta. La pregunta era cómo acceder a la variable externa sin hacerse definitiva. Y la solución es hacer una copia final. Y, por supuesto, es obvio que uno puede hacer una copia mutable adicional de la variable en el oyente. Pero primero no se le preguntó y segundo no requiere ningún initmétodo. Puedo agregar una línea de código adicional a mi ejemplo para tener esta variable adicional. Si eres un gran fanático de los patrones de construcción, no dudes en usarlos, pero en este caso no son necesarios.
ceving
No veo cómo esto es diferente a usar final una solución variable.
Kevin Rave
3

Puede usar lambdas simples ("las expresiones lambda pueden capturar variables")

int myVariable = 1;
ActionListener al = ae->System.out.println(myVariable);
myButton.addActionListener( al );

o incluso una función

Function<Integer,ActionListener> printInt = 
    intvar -> ae -> System.out.println(intvar);

int myVariable = 1;
myButton.addActionListener( printInt.apply(myVariable) );

Usar Function es una excelente manera de refactorizar decoradores y adaptadores, consulte aquí

Acabo de empezar a aprender sobre lambdas, así que si ves un error, no dudes en escribir un comentario.

ZiglioUK
fuente
1

¡Una manera simple de poner algo de valor en una variable externa (no pertenece a la clase anonymus) es cómo sigue!

De la misma manera, si desea obtener el valor de una variable externa, ¡puede crear un método que devuelva lo que desea!

public class Example{

    private TypeParameter parameter;

    private void setMethod(TypeParameter parameter){

        this.parameter = parameter;

    }

    //...
    //into the anonymus class
    new AnonymusClass(){

        final TypeParameter parameterFinal = something;
        //you can call setMethod(TypeParameter parameter) here and pass the
        //parameterFinal
        setMethod(parameterFinal); 

        //now the variable out the class anonymus has the value of
        //of parameterFinal

    });

 }
garzas
fuente
-2

Pensé que las clases anónimas eran básicamente como lambdas pero con peor sintaxis ... esto resulta ser cierto, pero la sintaxis es aún peor y hace que (lo que deberían ser) las variables locales se desvanezcan en la clase que las contiene.

No puede acceder a ninguna variable final convirtiéndolas en campos de la clase principal.

P.ej

Interfaz:

public interface TextProcessor
{
    public String Process(String text);
}

clase:

private String _key;

public String toJson()
{
    TextProcessor textProcessor = new TextProcessor() {
        @Override
        public String Process(String text)
        {
            return _key + ":" + text;
        }
    };

    JSONTypeProcessor typeProcessor = new JSONTypeProcessor(textProcessor);

    foreach(String key : keys)
    {
        _key = key;

        typeProcessor.doStuffThatUsesLambda();
    }

No sé si lo resolvieron en Java 8 (estoy atrapado en el mundo EE y todavía no tengo 8), pero en C # se vería así:

    public string ToJson()
    {
        string key = null;
        var typeProcessor = new JSONTypeProcessor(text => key + ":" + text);

        foreach (var theKey in keys)
        {
            key = theKey;

            typeProcessor.doStuffThatUsesLambda();
        }
    }

No necesita una interfaz separada en C # tampoco ... ¡Lo extraño! Me encuentro haciendo diseños peores en Java y repitiéndome más porque la cantidad de código + complejidad que tiene que agregar en Java para reutilizar algo es peor que simplemente copiar y pegar la mayor parte del tiempo.

JonnyRaa
fuente
parece que otro truco que puedes usar es tener una matriz de un elemento como se menciona aquí stackoverflow.com/a/4732586/962696
JonnyRaa