Clases anidadas (nested, inner, local, anónimas)

Avanzado
Java
Java
Actualizado: 18/04/2026

Cuatro tipos de clases anidadas

Java permite declarar una clase dentro de otra clase o método. Según dónde se declare y si captura la instancia externa, se clasifica en cuatro tipos:

| Tipo | Dónde se declara | Captura instancia externa | Uso típico | |------|------------------|---------------------------|------------| | Static nested class | Dentro de una clase, con static | No | Builders, iteradores, helpers | | Inner class | Dentro de una clase, sin static | Sí | Ligada a su contenedor | | Local class | Dentro de un método | Sí (si accede a variables) | Ayudas muy específicas | | Anonymous class | En una expresión | Sí (si accede) | Callbacks simples (pre-lambda) |

Static nested class

Una clase declarada dentro de otra con el modificador static. No guarda referencia implícita a una instancia de la clase contenedora: es como una clase top-level, pero vive dentro del namespace de la externa.

public class LinkedList<E> {
    private Nodo<E> primero;

    // Static nested: no depende de una LinkedList concreta
    private static class Nodo<E> {
        E valor;
        Nodo<E> siguiente;

        Nodo(E valor, Nodo<E> siguiente) {
            this.valor = valor;
            this.siguiente = siguiente;
        }
    }
}

Ventajas:

  • No consumen memoria extra (no hay referencia oculta al exterior).
  • Pueden instanciarse sin tener instancia externa.
  • Conceptualmente son clases helper con visibilidad limitada.

Caso clásico: Builder

public final class Persona {
    private final String nombre;
    private final int edad;
    private final String email;

    private Persona(Builder b) {
        this.nombre = b.nombre;
        this.edad = b.edad;
        this.email = b.email;
    }

    public static class Builder {
        private String nombre;
        private int edad;
        private String email;

        public Builder nombre(String n) { this.nombre = n; return this; }
        public Builder edad(int e) { this.edad = e; return this; }
        public Builder email(String e) { this.email = e; return this; }

        public Persona build() {
            return new Persona(this);
        }
    }
}

// Uso
Persona p = new Persona.Builder()
.nombre("Ana")
.edad(30)
.email("ana@example.com")
.build();

Inner class (non-static nested)

Una inner class captura una referencia a la instancia externa. Esa referencia se pasa automáticamente al constructor. La inner class puede acceder a los miembros privados del exterior.

public class Coche {
    private String modelo;
    private int kilometros;

    public class Motor {
        public String estado() {
            // Puede acceder directamente a modelo y kilometros del Coche externo
            return modelo + " con " + kilometros + " km";
        }
    }
}

Para crear una inner class necesitas una instancia del exterior:

Coche c = new Coche();
Coche.Motor m = c.new Motor();
System.out.println(m.estado());

Referenciar this externo

Dentro de una inner class, this es la instancia de la inner. Para acceder al exterior usa TipoExterno.this:

public class Contenedor {
    private int valor = 10;

    public class Hijo {
        private int valor = 20;

        public void imprimir() {
            System.out.println(this.valor); // 20 (Hijo)
            System.out.println(Contenedor.this.valor); // 10 (Contenedor)
        }
    }
}

Cuándo usar inner class

  • Cuando la clase tiene sentido solo en relación a una instancia específica del exterior.
  • Iteradores internos que necesitan acceso al estado del contenedor.
  • Listeners ligados a una instancia de componente UI.

Advertencia: la referencia oculta al exterior puede causar memory leaks si la inner sobrevive a su exterior (p.ej. registrada como callback global).

Local class

Una clase declarada dentro de un método. Solo es visible dentro del bloque donde se define. Puede acceder a variables locales del método si son efectivamente finales.

public List<String> procesar(List<Cliente> clientes) {
    String timestamp = Instant.now().toString(); // effectively final

    // Local class dentro del método
    class Reportador {
        String reporte(Cliente c) {
            return "[" + timestamp + "] " + c.nombre();
        }
    }

    Reportador r = new Reportador();
    return clientes.stream().map(r::reporte).toList();
}

Uso muy puntual. Hoy en día suele sustituirse por métodos privados o lambdas.

Anonymous class (clase anónima)

Una expresión que crea una clase sin nombre y una instancia en el mismo sitio. Era la forma previa a las lambdas de pasar comportamiento:

// Pre-lambda: clase anónima
List<String> lista = new ArrayList<>();
Collections.sort(lista, new Comparator<String>() {
        @Override
        public int compare(String a, String b) {
            return a.length() - b.length();
        }
    });

// Con lambda (Java 8+)
Collections.sort(lista, (a, b) -> a.length() - b.length());

Las lambdas son más concisas y eficientes para interfaces funcionales. Las clases anónimas siguen siendo útiles cuando:

  • Necesitas implementar varios métodos (no solo una interfaz funcional).
  • Necesitas estado interno (la lambda no tiene campos propios).
  • Quieres un tipo concreto (clase abstracta con métodos implementados).
// Anónima sigue siendo necesaria aquí: HashMap no es una interfaz funcional
Map<String, Integer> m = new HashMap<>() {{
        put("uno", 1);
        put("dos", 2);
        put("tres", 3);
    }};
// Nota: este patrón con double-brace initialization crea una inner class, úsalo con cuidado

Anónima extendiendo una clase

Thread t = new Thread("miHilo") {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("iter " + i);
        }
    }
};
t.start();

Visibilidad y modificadores

  • Static nested e inner classes pueden ser public, protected, private, o package-private.
  • Pueden ser final o abstract.
  • Una inner class no puede tener miembros static (excepto static final de tipos primitivos o String con valor constante conocido en compilación: regla relajada en Java 16+).
  • Static nested class puede tener miembros estáticos.

Cuándo usar cada una (guía práctica)

| Necesitas... | Usa | |--------------|-----| | Una clase helper que solo tiene sentido dentro de otra (builder, nodo, iterador) | Static nested class | | Una clase ligada lógicamente a una instancia externa (listener, componente de una UI) | Inner class (con cuidado) | | Implementar comportamiento una sola vez en un método | Lambda (preferido) | | Implementar comportamiento complejo con estado o múltiples métodos en un método | Clase anónima o local | | Un thread one-off | Lambda con Runnable o clase anónima extendiendo Thread |

Resumen

Las clases anidadas permiten estructurar código con visibilidad controlada y acoplamiento deliberado. En código moderno:

  • Static nested: muy usadas en builders, nodos internos, iteradores.
  • Inner: menos común; cuidado con leaks.
  • Locales: casi reemplazadas por métodos privados + lambdas.
  • Anónimas: casi reemplazadas por lambdas (salvo casos especiales).

Saber cuál elegir es señal de madurez en diseño OOP.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Diferenciar clase estática anidada, inner class, local y anónima. Usar inner classes para ligar comportamiento a una instancia externa. Declarar clases locales dentro de métodos. Reconocer cuándo una clase anónima es preferible a una lambda. Comprender la variable TipoExterno.this desde una inner class. Aplicar el patrón Builder con static nested class.