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
Regla de Effective Java (ítem 24): si una clase anidada no necesita acceder a la instancia de la clase externa, hazla estática. La inner class retiene una referencia a su instancia externa — si esa referencia se filtra (por ejemplo, pasando la inner class a un callback de larga vida), la clase externa no puede ser recolectada por el GC.

Siguiente apartado → Interfaces frente a Clases Abstractas

Índice de la sección

Índice del curso