W3docs

Clases anidadas en Java

Clases anidadas en Java: clases estáticas anidadas, clases internas, clases locales y clases anónimas explicadas con ejemplos.

Java permite declarar una clase dentro de otra clase. El término general es clase anidada, y Java ofrece cuatro variantes, divididas según dónde se ubican y si llevan una referencia a una instancia envolvente:

VarianteDónde se declara¿Lleva this envolvente?Capítulo
Clase estática anidadaDentro del cuerpo de una clase, con staticNoeste capítulo
Clase internaDentro del cuerpo de una clase, sin staticclases internas
Clase localDentro del cuerpo de un métodoSí (si el método no es estático)clases locales
Clase anónimaUna subclase/implementación creada en el momentoSí (si el contexto no es estático)clases anónimas

La razón para elegir una clase anidada en lugar de una clase de nivel superior separada es el alcance — la clase anidada solo es útil en el contexto de su clase envolvente y no debería exponerse al resto del código. Este capítulo es el resumen general; cada tipo tiene su propio tratamiento más detallado en los capítulos siguientes.

¿Por qué anidar clases?

Tres motivaciones principales:

  • Agrupación lógica. Un Map.Entry solo tiene sentido dentro de un Map. Anidarlo hace que esa relación sea obvia en el código.
  • Encapsulación. Una clase anidada puede ser private, de modo que nada fuera de la clase envolvente pueda hacer referencia a ella.
  • Clausuras sobre el estado envolvente. Una clase interna / local / anónima puede leer los campos de la instancia envolvente y las variables locales del método, lo cual es la base de los manejadores de eventos, iteradores y muchos pequeños patrones adaptadores.

Si ninguna de estas razones aplica, escribe una clase de nivel superior.

Clases estáticas anidadas

Una clase marcada con static dentro de otra clase es una clase estática anidada:

public class Outer {
  static class Inner {
    void hi() { System.out.println("hi"); }
  }
}

Outer.Inner i = new Outer.Inner();    // instantiate directly
i.hi();

Una clase estática anidada es simplemente una clase de nivel superior que vive dentro del espacio de nombres de Outer. No tiene referencia implícita a una instancia de Outer — puedes usarla sin crear ninguna. La única diferencia con una clase de nivel superior es el alcance: Outer.Inner es el nombre calificado.

Esta es la variante que ya has visto a lo largo de las Partes 5 y 6 — cada static class Foo {} en un ejemplo RunnableJava es una de estas. La razón por la que usamos static fue para poder instanciarlas desde un main estático sin necesitar una instancia de Outer.

Clases internas (no estáticas anidadas)

Quita el static y obtienes una clase interna. La instancia de la clase interna está ligada a una instancia de la clase exterior, llevando una referencia implícita a ella:

public class Outer {
  int x = 1;
  class Inner {                       // no static
    int get() { return x; }           // reads Outer's x through the implicit reference
  }
}

Outer       o = new Outer();
Outer.Inner i = o.new Inner();        // unusual syntax — bind to o
System.out.println(i.get());           // 1

La curiosa sintaxis o.new Inner() es como se crea una instancia de clase interna ligada a una instancia exterior específica. Las clases internas no pueden tener miembros static (regla antigua; relajada en Java 16+ para permitir miembros static en clases internas). Se trata en detalle en clases internas.

Clases locales

Una clase declarada dentro del cuerpo de un método es local — con alcance en ese método:

public void run() {
  class Step { int n; Step(int n) { this.n = n; } }     // visible only in run()

  Step s = new Step(5);
}

Útiles para pequeños auxiliares que no merecen una clase de nivel superior ni siquiera una anidada a nivel de clase. Tienen acceso a las variables final (o efectivamente finales) del método envolvente. El capítulo de clases locales tiene la historia completa.

Clases anónimas

Una clase anónima es una subclase o implementación de interfaz de un solo uso, definida en línea en el punto de uso:

Runnable r = new Runnable() {
  @Override
  public void run() { System.out.println("hi"); }
};

Esa es una única expresión que (1) define una nueva clase que implementa Runnable, (2) crea una instancia de ella, (3) la asigna a r. La clase no tiene nombre — solo existe aquí. Casi todos los casos de uso para clases anónimas han sido reemplazados por lambdas en Java moderno, pero siguen siendo válidas y ocasionalmente útiles. Véase clases anónimas.

Elegir el tipo correcto

Un árbol de decisión rápido:

  1. ¿La clase anidada necesita una referencia a una instancia exterior? Si no → clase estática anidada. Si sí → alguna variante no estática.
  2. ¿Se usa dentro de un único método? Si sí → local o anónima. Si no → interna.
  3. ¿Es una subclase/implementación de interfaz de un solo uso con uno o dos métodos? Si sí → lambda (preferida) o clase anónima (legado).

Las clases estáticas anidadas son con diferencia las más comunes. Las clases internas aparecen en adaptadores de estilo iterador. Las clases locales y anónimas son más raras en el código moderno — las lambdas consumen la mayoría de sus casos de uso.

Nomenclatura y acceso

Las clases anidadas pueden llevar cualquier modificador de acceso — public, private, protected, package-private — y las reglas del modificador son las mismas que para los miembros de nivel superior. Map.Entry es public; un private static class Node dentro de una implementación de LinkedList es invisible para el mundo exterior.

Las clases anidadas compiladas obtienen nombres separados por $ en los archivos .class: Outer$Inner, Outer$1. Los verás ocasionalmente en trazas de pila y depuradores.

Un ejemplo práctico

java— editable, runs on the server

¿Qué sigue?

Los tres capítulos siguientes profundizan en cada uno de los tipos no estáticos. Primero: clases internas, las más generales — una clase ligada a una instancia envolvente.

Práctica

Práctica
¿Qué tipo de clase anidada NO lleva una referencia implícita a una instancia de la clase envolvente?
¿Qué tipo de clase anidada NO lleva una referencia implícita a una instancia de la clase envolvente?
Was this page helpful?