Java 48: Nested, Inner, Local & Anonymous Classes
easy⏱ 5 mincoursejava
Static nested vs inner
Una clase static nested (static class Node) es solo una clase top-level con scope dentro de otra — sin enlace a una instancia externa, se instancia con Outer.Node. Una clase inner (no estática, class Iter) guarda una referencia implícita Outer.this, así que lee los campos del externo — pero solo puede crearse desde una instancia externa (outer.new Iter()) y mantiene vivo al externo para el GC. Usa static por defecto salvo que necesites de verdad la instancia envolvente.
class Outer {
private int x = 10;
static class Nested { /* no Outer.this */ }
class Inner { int read() { return x; } } // captures Outer.this
}
Outer.Nested n = new Outer.Nested();
Outer o = new Outer();
Outer.Inner i = o.new Inner();
Clases locales y anónimas
Una clase local se declara dentro del cuerpo de un método; una clase anónima (new Runnable() { ... }) es una clase local sin nombre, instanciada inline — perfecta para implementaciones de interfaz de un solo uso antes de las lambdas. Ambas pueden capturar variables locales, pero solo si son final o effectively final (nunca reasignadas), porque el valor capturado se copia en la instancia de la clase.
Runnable r = new Runnable() { // anonymous class
public void run() { System.out.println(prefix); } // captures prefix
};
// prefix must be effectively final
this y shadowing
Dentro de una clase interna/anónima, this refiere a la instancia interna; alcanza la instancia envolvente con Outer.this. Una lambda es distinta: no tiene this propio y captura el this envolvente — por eso las lambdas no pueden ser auto-referenciales y por eso son más baratas que las clases anónimas (sin clase extra, sin shadowing de la instancia capturada).
Modela las cuatro formas anidadas
Construye Outer con un campo privado secret. (1) Una Box static-nested que NO ve secret. (2) Un Reader interno creado desde una instancia externa que SÍ lee secret (captura el externo en su constructor para modelar Outer.this). (3) Un método que devuelve un Greeter anónimo que captura un name effectively-final. (4) Una factory que crea un contador vía un local capturado. Imprime cada resultado y confirma que la clase interna lee el secret del externo.
Las clases internas filtran su externo
Como una instancia interna no estática fija su Outer.this, guardar una instancia interna (p. ej. un listener o Runnable) en una colección de larga vida mantiene todo el objeto externo — y todo lo que él referencia — sin poder recolectarse. La fuga clásica de Android/Swing. Solución: haz la clase anidada static y pásale solo los datos que necesita.
Quiz: ¿por qué effectively final?
¿Por qué los locales capturados deben ser (effectively) final? La instancia de la clase captura una copia del valor al crearse, no la variable viva — no hay celda mutable compartida. Permitir reasignación haría que la copia interna y la variable externa divergieran en silencio. ¿Necesitas mutación compartida? Usa un arreglo de un elemento o un AtomicInteger.