Como passar parâmetros para classe anônima?

146

É possível passar parâmetros ou acessar parâmetros externos para uma classe anônima? Por exemplo:

int myVariable = 1;

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

Existe alguma maneira de o ouvinte acessar myVariable ou passar para myVariable sem criar o ouvinte como uma classe nomeada real?

Lewis
fonte
7
Você pode referenciar finalvariáveis ​​locais a partir do método anexo.
Tom Hawtin - tackline
Gosto da aparência da sugestão de Adam Mmlodzinski de definir um método privado que inicialize instâncias privadas myVariable e possa ser chamado na chave de fechamento devido ao retorno this.
precisa saber é o seguinte
Esta pergunta tem alguns objetivos compartilhados de: stackoverflow.com/questions/362424/…
Alastair McCormack:
Você também pode usar as variáveis ​​de classe global de dentro da classe anônima. Talvez não seja muito limpo, mas pode fazer o trabalho.
Jori

Respostas:

78

Tecnicamente, não, porque classes anônimas não podem ter construtores.

No entanto, as classes podem fazer referência a variáveis ​​que contêm escopos. Para uma classe anônima, essas podem ser variáveis ​​de instância das classes que contêm ou variáveis ​​locais marcadas como finais.

edit : Como Peter apontou, você também pode passar parâmetros para o construtor da superclasse da classe anônima.

Matthew Willis
fonte
21
Um uso de classe anônima usa os construtores de seu pai. por exemplonew ArrayList(10) { }
Peter Lawrey 24/02
Bom ponto. Portanto, essa seria outra maneira de passar um parâmetro para uma classe anônima, embora seja provável que você não tenha controle sobre esse parâmetro.
Matthew Willis
classes anônimas não precisam de construtores
newacct
4
As classes anônimas podem ter inicializadores de instância, que podem funcionar como construtores sem parâmetros em classes anônimas. Eles são executados na mesma ordem que as designações de campo, ou seja, super()antes e depois do restante do construtor real. new someclass(){ fields; {initializer} fields; methods(){} }. É como um inicializador estático, mas sem a palavra-chave estática. docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6
Mark Jeronimus
Veja este stackoverflow.com/a/3045185/1737819 que diz como implementar sem construtor.
Developer Marius Žilėnas
336

Sim, adicionando um método inicializador que retorna 'this' e chamando imediatamente esse 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)  );

Nenhuma declaração 'final' é necessária.

Adam Mlodzinski
fonte
4
uau ... brilhante! Estou tão cansado de criar um finalobjeto de referência para poder obter informações em minhas classes anônimas. Obrigado por compartilhar!
Matt Klein
7
Por que a init()função precisa retornar this? Eu realmente não entendo a sintaxe.
Jori
11
porque o meu myButton.addActionListener (...) espera um objeto ActionListener como um objeto retornado quando você chama seu método.
1
Eu acho .. acho isso muito feio, embora funcione. Acho que na maioria das vezes posso simplesmente dar ao luxo de finalizar as variáveis ​​e parâmetros de função necessários e referenciá-los diretamente da classe interna, pois geralmente eles estão apenas sendo lidos.
Thomas
2
Mais simples: private int anonVar = myVariable;
Anm
29

sim. você pode capturar variável visível para a classe interna. a única limitação é que deve ser final

aav
fonte
As variáveis ​​de instância referenciadas de uma classe anônima não precisam ser finalizadas.
Matthew Willis
8
Variáveis ​​de instância são referenciadas através da thisqual é final.
Peter Lawrey
E se eu não quiser que a variável seja alterada final? Não consigo encontrar outra alternativa. Isso pode influenciar o parâmetro de origem projetado para ser final.
Alston
20

Como isso:

final int myVariable = 1;

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

Isso fará a mágica

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

fonte
8

Conforme mostrado em http://www.coderanch.com/t/567294/java/java/declare-constructor-anonymous-class, você pode adicionar um inicializador de instância. É um bloco que não tem nome e é executado primeiro (como um construtor).

Parece que eles também são discutidos em Por que inicializadores de instância java? e Como um inicializador de instância é diferente de um construtor? discute as diferenças dos construtores.

Rob Russell
fonte
Isso não resolve a pergunta que está sendo feita. Você ainda terá o problema de acesso a variáveis locais, de forma que você nem precisa de usar a solução de Adão Mlodzinski ou adarshr
Matt Klein
1
@ MattKlein Para mim, parece que resolve. É a mesma coisa, na verdade, e menos detalhada.
haelix
1
A pergunta queria saber como passar parâmetros para a classe, como faria com um construtor que requer parâmetros. O link (que deveria ter sido resumido aqui) mostra apenas como ter um inicializador de instância sem parâmetro, o que não responde à pergunta. Essa técnica pode ser usada com finalvariáveis ​​descritas por aav, mas essas informações não foram fornecidas nesta resposta. De longe, a melhor resposta é a dada por Adam Mlodzinksi (agora uso esse padrão exclusivamente, não há mais finais!). Eu mantenho meu comentário de que isso não responde à pergunta.
Matt Klein
7

Minha solução é usar um método que retorne a classe anônima implementada. Argumentos regulares podem ser passados ​​para o método e estão disponíveis na classe anônima.

Por exemplo: (de algum código GWT para lidar com uma alteração na caixa 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 exemplo, o novo método de classe anônimo seria referenciado com:

textBox.addChangeHandler(newNameChangeHandler(myAxisName, myLogger))

OU , usando os requisitos do 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
fonte
Isso é bom, restringe a classe anônima a algumas variáveis ​​fáceis de identificar e elimina as abominações de ter que finalizar algumas variáveis.
Variável miserável
3

Outras pessoas já responderam que classes anônimas podem acessar apenas variáveis ​​finais. Mas eles deixam a questão em aberto como manter a variável original não final. Adam Mlodzinski deu uma solução, mas é bastante inchada. Existe uma solução muito mais simples para o problema:

Se você não quiser myVariableser final, precisará envolvê-lo em um novo escopo, onde isso não importa, se for final.

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 não faz mais nada em sua resposta, mas com muito mais código.

ceving
fonte
Isso ainda funciona sem o escopo extra. É efetivamente o mesmo que as outras respostas usando final.
Adam Mlodzinski
@AdamMlodzinski Não, é efetivamente o mesmo que sua resposta, porque introduz uma nova variável com o valor da variável original em um escopo privado.
ceving 02/11/2013
Não é efetivamente o mesmo. No seu caso, sua classe interna não pode fazer alterações no anonVar - portanto, o efeito é diferente. Se sua classe interna tiver que, digamos, manter algum estado, seu código precisará usar algum tipo de Objeto com um setter em vez de um primitivo.
Adam Mlodzinski
@AdamMlodzinski Essa não era a questão. A questão era como acessar a variável externa sem se tornar final. E a solução é fazer uma cópia final. E é claro que é óbvio que se pode fazer uma cópia mutável adicional da variável no ouvinte. Mas, primeiro, não foi solicitado e, segundo, não requer nenhum initmétodo. Eu posso adicionar uma linha de código adicional ao meu exemplo para ter essa variável adicional. Se você é um grande fã de padrões de construtores, sinta-se à vontade para usá-los, mas eles não são necessários neste caso.
ceving 21/11/2013
Não vejo como isso é diferente do que usar finalsolução variável.
Kevin Rave
3

Você pode usar lambdas simples ("expressões lambda podem capturar variáveis")

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

ou mesmo uma função

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

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

Usar a Função é uma ótima maneira de refatorar decoradores e adaptadores, veja aqui

Acabei de começar a aprender sobre lambdas, por isso, se você encontrar um erro, fique à vontade para escrever um comentário.

ZiglioUK
fonte
1

Uma maneira simples de colocar algum valor em uma variável externa (não pertence à classe anonymus) é como segue!

Da mesma forma, se você deseja obter o valor de uma variável externa, pode criar um método que retorne o que deseja!

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

    });

 }
garças
fonte
-2

Eu pensei que classes anônimas eram basicamente como lambdas, mas com sintaxe pior ... isso acaba sendo verdade, mas a sintaxe é ainda pior e faz com que (o que deveria ser) variáveis ​​locais vazem na classe que o contém.

Você não pode acessar nenhuma variável final transformando-a em campos da classe pai.

Por exemplo

Interface:

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

classe:

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

Eu não sei se eles resolveram isso em java 8 (eu estou preso no mundo EE e ainda não tenho 8), mas em c # ficaria assim:

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

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

            typeProcessor.doStuffThatUsesLambda();
        }
    }

Você não precisa de uma interface separada em c # também ... Eu sinto falta! Eu me pego criando designs piores em java e me repetindo mais porque a quantidade de código + complexidade que você precisa adicionar em java para reutilizar algo é pior do que apenas copiar e colar a maior parte do tempo.

JonnyRaa
fonte
parece outro truque que você pode usar é ter uma matriz elemento como mencionado aqui stackoverflow.com/a/4732586/962696
JonnyRaa