Com classes anônimas, você está realmente declarando uma classe aninhada "sem nome". Para classes aninhadas, o compilador gera uma nova classe pública independente com um construtor que aceita todas as variáveis usadas como argumentos (para classes aninhadas "nomeadas", essa é sempre uma instância da classe original / anexa). Isso é feito porque o ambiente de tempo de execução não tem noção de classes aninhadas, portanto, é necessário haver uma conversão (automática) de uma classe aninhada para uma classe independente.
Veja este código, por exemplo:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Isso não vai funcionar, porque é isso que o compilador faz sob o capô:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
A classe anônima original é substituída por alguma classe autônoma que o compilador gera (o código não é exato, mas deve lhe dar uma boa idéia):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Como você pode ver, a classe autônoma mantém uma referência ao objeto compartilhado; lembre-se de que tudo em java é transmitido por valor; portanto, mesmo que a variável de referência 'compartilhada' em EnclosingClass seja alterada, a instância para a qual ela aponta não será modificada , e todas as outras variáveis de referência que apontam para ele (como a da classe anônima: Incluindo $ 1), não estarão cientes disso. Esse é o principal motivo pelo qual o compilador obriga a declarar essas variáveis 'compartilhadas' como finais, para que esse tipo de comportamento não seja inserido no código já em execução.
Agora, é isso que acontece quando você usa uma variável de instância dentro de uma classe anônima (é o que você deve fazer para resolver seu problema, mova sua lógica para um método de "instância" ou para um construtor de uma classe):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Isso compila bem, porque o compilador modifica o código, para que a nova classe gerada Enclosing $ 1 mantenha uma referência à instância de EnclosingClass em que foi instanciada (isso é apenas uma representação, mas você deve continuar):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
Assim, quando a variável de referência 'compartilhada' em EnclosingClass for reatribuída, e isso acontecer antes da chamada para o Thread # run (), você verá "other hello" impresso duas vezes, porque agora a variável envolvente EnclosingClass $ 1 # manterá uma referência para o objeto da classe em que foi declarado, portanto, as alterações em qualquer atributo desse objeto serão visíveis para as instâncias de EnclosingClass $ 1.
Para mais informações sobre o assunto, você pode ver esta excelente postagem no blog (não escrita por mim): http://kevinboone.net/java_inner.html