a
só pode ser final aqui. Por quê? Como posso transferira
noonClick()
método sem mantê-lo como membro privado?private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
Como posso devolver
5 * a
quando clicou? Quero dizer,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 } }); }
java
event-handling
anonymous-class
Andrii Abramov
fonte
fonte
Respostas:
Conforme observado nos comentários, parte disso se torna irrelevante no Java 8, onde
final
pode estar implícito. Apenas uma variável efetivamente final pode ser usada em uma classe interna anônima ou expressão lambda.Isso se deve basicamente à maneira como o Java gerencia os fechamentos .
Quando você cria uma instância de uma classe interna anônima, todas as variáveis usadas nessa classe têm seus valores copiados por meio do construtor gerado automaticamente. Isso evita que o compilador precise gerar automaticamente vários tipos extras para manter o estado lógico das "variáveis locais", como por exemplo o compilador C # ... (Quando o C # captura uma variável em uma função anônima, ele realmente captura a variável - o O encerramento pode atualizar a variável de uma maneira que é vista pelo corpo principal do método e vice-versa.)
Como o valor foi copiado para a instância da classe interna anônima, seria estranho se a variável pudesse ser modificada pelo restante do método - você poderia ter um código que parecia estar funcionando com uma variável desatualizada ( porque é efetivamente o que estaria acontecendo ... você estaria trabalhando com uma cópia tirada em outro momento). Da mesma forma, se você puder fazer alterações na classe interna anônima, os desenvolvedores podem esperar que essas alterações sejam visíveis no corpo do método que o envolve.
Tornar a variável final remove todas essas possibilidades - como o valor não pode ser alterado, você não precisa se preocupar se essas alterações serão visíveis. As únicas maneiras de permitir que o método e a classe interna anônima vejam as mudanças um do outro é usar um tipo mutável de alguma descrição. Pode ser a própria classe envolvente, uma matriz, um tipo de invólucro mutável ... qualquer coisa assim. Basicamente, é um pouco como se comunicar entre um método e outro: as alterações feitas nos parâmetros de um método não são vistas pelo responsável pela chamada, mas as alterações feitas nos objetos referidos pelos parâmetros são vistas.
Se você estiver interessado em uma comparação mais detalhada entre os fechamentos Java e C #, eu tenho um artigo que vai além. Eu queria me concentrar no lado Java nesta resposta :)
fonte
final
(mas ainda precisa ser efetivamente final).Existe um truque que permite que a classe anônima atualize dados no escopo externo.
No entanto, esse truque não é muito bom devido aos problemas de sincronização. Se o manipulador for chamado posteriormente, você precisará 1) sincronizar o acesso ao res se o manipulador for chamado a partir do encadeamento diferente 2) precisar de algum tipo de sinalizador ou indicação de que o res foi atualizado
Esse truque funciona bem, no entanto, se a classe anônima for invocada no mesmo encadeamento imediatamente. Gostar:
fonte
List.forEach
código é seguro.Uma classe anônima é uma classe interna e a regra estrita se aplica a classes internas (JLS 8.1.3) :
Ainda não encontrei uma razão ou explicação sobre as jls ou jvms, mas sabemos que o compilador cria um arquivo de classe separado para cada classe interna e deve garantir que os métodos declarados nesse arquivo de classe ( no nível do código de bytes) pelo menos tenha acesso aos valores das variáveis locais.
( Jon tem a resposta completa - eu mantenho esta desmarcada porque alguém pode estar interessado na regra JLS)
fonte
Você pode criar uma variável no nível da classe para obter o valor retornado. Quero dizer
agora você pode obter o valor de K e usá-lo onde quiser.
Resposta do seu porque é:
Uma instância local da classe interna está vinculada à classe Main e pode acessar as variáveis locais finais do método que a contém. Quando a instância usa um local final do método que a contém, a variável retém o valor que ela mantinha no momento da criação da instância, mesmo que a variável tenha saído do escopo (essa é efetivamente a versão bruta e limitada de fechamentos de Java).
Como uma classe interna local não é membro de uma classe ou pacote, ela não é declarada com um nível de acesso. (Seja claro, no entanto, que seus próprios membros têm níveis de acesso como em uma classe normal.)
fonte
Bem, em Java, uma variável pode ser final não apenas como parâmetro, mas como um campo no nível da classe, como
ou como uma variável local, como
Se você deseja acessar e modificar uma variável de uma classe anônima, convém tornar a variável uma variável de nível de classe na classe envolvente .
Você não pode ter uma variável como final e atribuir um novo valor.
final
significa exatamente isso: o valor é imutável e final.E, como é final, o Java pode copiá- lo com segurança para classes anônimas locais. Você não está obtendo alguma referência ao int (especialmente porque você não pode ter referências a primitivas como int em Java, apenas referências a objetos ).
Ele apenas copia o valor de a em um int implícito chamado a na sua classe anônima.
fonte
static
. Talvez seja mais claro se você usar "variável de instância".A razão pela qual o acesso foi restrito apenas às variáveis finais locais é que, se todas as variáveis locais fossem tornadas acessíveis, elas primeiro precisariam ser copiadas para uma seção separada onde as classes internas possam ter acesso a elas e manter várias cópias de variáveis locais mutáveis podem levar a dados inconsistentes. Considerando que as variáveis finais são imutáveis e, portanto, qualquer número de cópias para elas não terá impacto na consistência dos dados.
fonte
Int
está sendo transferido para dentro de um fechamento, altere essa variável para uma instância deIntRef
(essencialmente umInteger
invólucro mutável ). Todo acesso à variável é reescrito de acordo.Para entender a lógica dessa restrição, considere o seguinte programa:
O interfaceInstance permanece na memória após o retorno do método initialize , mas o parâmetro val não. A JVM não pode acessar uma variável local fora de seu escopo; portanto, Java faz a chamada subseqüente para printInteger funcionar, copiando o valor de val para um campo implícito com o mesmo nome em interfaceInstance . O interfaceInstance se diz ter capturado o valor do parâmetro local. Se o parâmetro não fosse final (ou efetivamente final), seu valor poderia mudar, ficando fora de sincronia com o valor capturado, potencialmente causando um comportamento não intuitivo.
fonte
Os métodos dentro de uma classe interna anônima podem ser invocados bem após o término do encadeamento que o gerou. No seu exemplo, a classe interna será chamada no segmento de despacho de eventos e não no mesmo segmento que o que o criou. Portanto, o escopo das variáveis será diferente. Portanto, para proteger esses problemas de escopo de atribuição de variáveis, declare-os finais.
fonte
Quando uma classe interna anônima é definida no corpo de um método, todas as variáveis declaradas finais no escopo desse método são acessíveis de dentro da classe interna. Para valores escalares, uma vez atribuídos, o valor da variável final não pode ser alterado. Para valores de objeto, a referência não pode ser alterada. Isso permite que o compilador Java "capture" o valor da variável no tempo de execução e armazene uma cópia como um campo na classe interna. Depois que o método externo termina e o quadro da pilha é removido, a variável original desaparece, mas a cópia privada da classe interna persiste na memória da própria classe.
( http://en.wikipedia.org/wiki/Final_%28Java%29 )
fonte
fonte
Como Jon tem os detalhes da implementação, uma outra resposta possível seria que a JVM não deseja manipular a gravação em registro que encerrou sua ativação.
Considere o caso de uso em que suas lambdas, em vez de serem aplicadas, são armazenadas em algum lugar e executadas posteriormente.
Lembro-me de que em Smalltalk você obteria uma loja ilegal levantada ao fazer essa modificação.
fonte
Experimente este código,
Crie uma lista de matrizes e coloque valor dentro dela e retorne-o:
fonte
A classe anônima Java é muito semelhante ao fechamento do Javascript, mas o Java implementa isso de maneira diferente. (verifique a resposta de Andersen)
Portanto, para não confundir o Java Developer com o comportamento estranho que pode ocorrer para quem vem do fundo do Javascript. Eu acho que é por isso que eles nos forçam a usar
final
, essa não é a limitação da JVM.Vejamos o exemplo de Javascript abaixo:
Em Javascript, o
counter
valor será 100, porque existe apenas umcounter
variável do começo ao fim.Mas em Java, se não houver
final
, ele será impresso0
, porque enquanto o objeto interno está sendo criado, o0
valor é copiado para as propriedades ocultas do objeto da classe interna. (existem duas variáveis inteiras aqui, uma no método local e outra nas propriedades ocultas da classe interna)Portanto, qualquer alteração após a criação do objeto interno (como a linha 1), não afetará o objeto interno. Portanto, criará confusão entre dois resultados e comportamentos diferentes (entre Java e Javascript).
Acredito que é por isso que o Java decide forçá-lo a ser final, para que os dados sejam 'consistentes' do começo ao fim.
fonte
Variável final Java dentro de uma classe interna
classe interna pode usar apenas
Object
...)int
...) pode ser agrupado por um tipo de referência final.IntelliJ IDEA
pode ajudá-lo a encobri-lo em uma matriz de elementoQuando a
non static nested
(inner class
) [About] é gerado pelo compilador - uma nova classe -<OuterClass>$<InnerClass>.class
é criada e os parâmetros vinculados são passados para o construtor [variável local na pilha] . É semelhante ao fechamentovariável final é uma variável que não pode ser reatribuída. A variável de referência final ainda pode ser alterada modificando um estado
Era possível que fosse estranho, porque como programador você poderia fazer assim
fonte
Talvez esse truque te dê uma idéia
fonte