Clases internas, envolventes y estáticas
Usos y restricciones
Clase interna (inner class)
Una clase definida dentro de otra que mantiene una referencia implícita a la instancia de la clase externa. Puede acceder a todos sus miembros, incluyendo los privados. El inconveniente es que no puede instanciarse sin una instancia de la externa, y la referencia implícita puede causar fugas de memoria.
// Clase interna (inner class): instancia ligada a la instancia de la clase externa
public class ClaseExterna {
private int valor = 10;
class ClaseInterna {
void mostrar() {
System.out.println(valor); // accede a miembros de la clase externa
}
}
void usar() {
ClaseInterna ci = new ClaseInterna(); // se crea ligada a "this"
ci.mostrar();
}
}
// Desde fuera:
ClaseExterna ext = new ClaseExterna();
ClaseExterna.ClaseInterna inner = ext.new ClaseInterna(); // sintaxis especial Clase interna estática (static nested)
Declarada con static. No tiene referencia implícita a la clase externa y se instancia de forma
independiente. Es la opción preferida cuando la clase anidada no necesita acceder al estado de la externa.
Effective Java la recomienda sobre la inner class salvo necesidad explícita.
// Clase interna estática (static nested class): NO tiene referencia implícita a la externa
public class Nodo<T> {
T valor;
Nodo<T> siguiente;
// No necesita acceder al estado de la clase externa → static
static class Builder<T> {
private T valor;
Builder<T> valor(T v) { this.valor = v; return this; }
Nodo<T> build() {
Nodo<T> n = new Nodo<>();
n.valor = valor;
return n;
}
}
}
// Se instancia sin necesitar una instancia de Nodo
Nodo.Builder<String> builder = new Nodo.Builder<>(); Clase anónima
Clase sin nombre declarada e instanciada en el mismo lugar. Era el patrón estándar para pasar comportamiento antes de Java 8. Con lambdas su uso se ha reducido casi exclusivamente a casos donde se necesita implementar una interfaz con más de un método o extender una clase abstracta.
// Clase anónima: clase sin nombre definida e instanciada en un solo lugar
// Antes de Java 8 era la única forma de pasar comportamiento como argumento
Comparator<String> porLongitud = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
};
// Con Java 8, la lambda reemplaza esto casi siempre:
Comparator<String> porLongitud2 = (a, b) -> Integer.compare(a.length(), b.length()); Clase local
Definida dentro de un método. Puede acceder a las variables locales del método si son efectivamente finales (no se reasignan). Hoy tiene un uso marginal — las lambdas cubren la mayoría de sus casos de uso de forma más concisa.
// Clase local: definida dentro de un método, visible solo en ese método
void procesarDatos(List<String> datos) {
// Clase local: tiene acceso a variables locales efectivamente finales
class Procesador {
String procesar(String s) {
return s.trim().toLowerCase();
}
}
Procesador p = new Procesador();
datos.stream()
.map(p::procesar)
.forEach(System.out::println);
}
// Hoy raramente se usa — las lambdas cubren casi todos los casos de uso Cuándo usar cada tipo
// Cuándo usar cada tipo:
// Clase interna estática (static nested):
// → cuando la clase auxiliar no necesita acceder a instancia de la externa
// → builders, entradas de mapas, nodos de estructuras de datos
// Clase interna (inner):
// → cuando necesita acceso implícito a la instancia externa
// → iteradores personalizados, event handlers ligados al objeto contenedor
// Clase anónima:
// → raramente en Java 8+, sustituida por lambdas
// → útil cuando se extiende una clase abstracta con más de un método
// Clase local:
// → raramente, cuando se necesita una clase auxiliar solo en un método