Permitir a coleta de lixo de uma aula enquanto uma instância de classe interna anônima é referenciada em outro lugar?
-
20-09-2019 - |
Pergunta
Eu tenho uma classe A:
public class A {
private B b = new B() { public void method() { do something } };
public B getB() { return b; }
}
public interface B { void method(); }
A instância b
tem uma referência implícita da instância de sua classe externa (que pode ser referenciada por this
). Agora outro objeto recebe uma referência a isso b
através do método getter. este b
não pode ser coletado de lixo devido à referência.
Existe uma maneira de obter a possibilidade de permitir uma coleta de lixo dos anexos A
Por exemplo, talvez através da redefinição de uma referência explícita na classe interna anônima?
Solução
É tecnicamente possível:
public class HasInner {
public static interface Foo {}
private static <T> T release(T instance, Object ref) {
try {
Class<?> type = instance.getClass();
for (Field field : type.getFields()) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
if (field.get(instance) == ref) {
field.set(instance, null);
}
}
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
return instance;
}
public Foo makeFoo() {
return release(new Foo() {}, this);
}
public static void main(String[] args) {
new HasInner().makeFoo();
}
}
o javap Inspeção da aula anônima:
Compiled from "HasInner.java"
final class HasInner$1 extends java.lang.Object implements HasInner$
Foo{
final HasInner this$0;
HasInner$1(HasInner);
}
A implementação não depende do nome de campo sendo this$0
Como eu suspeito, este é um detalhe de implementação do compilador.
Áreas problemáticas em potencial:
- Um gerente de segurança pode proibir o código de reflexão.
- Eu não acho A plataforma Java define exatamente como o tipo interno se refere à externa. Ou seja, é um detalhe de implementação do compilador e seria legal, se estúpido, ter um invólucro intermediário no campo - na presença de outros campos, desambiguar a referência pode ser impossível.
Em suma, eu faria nunca faça isso.
Se isso é uma preocupação, Use uma classe interna estática privada:
public class A {
private static class BImpl implements B {
@Override public void method() {
}
}
private final B b = new BImpl();
public B getB() { return b; }
}
Outras dicas
Após as chamadas do seu código B ref = (new A()).getB()
O heap java conterá uma variável ref
que aponta para o mesmo objeto anônimo que (new A()).b
que por sua vez tem uma referência interna ao seu anexo new A()
. Nenhuma outra referência ao new A()
Objeto existe.
Sua pergunta é: como podemos forçar uma coleção de lixo do objeto A, mantendo o objeto B anônimo vivo? A resposta é que você não pode, porque se pudesse, o que aconteceria com o código em B que usa a referência interna a A?
Se você souber que o código da sua classe B não faz referência à sua classe de anexo, pode declará -lo estático, o que significa que não receberá uma referência interna à sua classe de cercadores. Para fazer isso, você precisa torná -lo uma aula aninhada, pois não pode spaecificar aulas anônimas para ser estática:
public class A {
static class Bimpl implements B { public void method() { do something } };
private B b = new Bimpl();
public B getB() { return b; }
}
public interface B { void method(); }
Se o seu código ligar B ref = (new A()).getB()
Nesta situação, o new A()
O objeto estará disponível para a coleta de lixo, pois não existe referência a ele.