W3docs

Clases anónimas internas en Java

Crea implementaciones únicas de interfaces y clases abstractas en Java con clases anónimas internas.

Una clase anónima es una subclase de un solo uso o una implementación de interfaz que defines e instancias en una sola expresión — sin nombre, sin archivo separado, sin encabezado de clase. Así es como Java manejaba callbacks, listeners y pequeños adaptadores antes de que llegaran las expresiones lambda en Java 8. Las lambdas han reemplazado la mayoría de sus casos de uso, pero las clases anónimas siguen siendo válidas, siguen siendo útiles en algunas situaciones específicas y aún aparecen en bases de código antiguas.

La sintaxis

La forma es new SomeType() { ... body ... }. El cuerpo es la definición de la clase; la expresión circundante también crea una instancia:

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

Esa única instrucción (1) define una nueva clase que implementa Runnable, (2) crea una instancia de ella, (3) asigna la instancia a r. La clase no tiene nombre en el código fuente; el compilador le da un nombre generado como Outer$1 en el archivo .class.

Puedes hacer lo mismo con una clase abstracta:

Shape s = new Shape() {
  @Override double area() { return 42; }
};

…o incluso con una clase concreta, si quieres sobrescribir uno de sus métodos en línea:

ArrayList<String> chatty = new ArrayList<>() {
  @Override
  public boolean add(String s) {
    System.out.println("added " + s);
    return super.add(s);
  }
};

Cuándo usarlas

El caso clásico: un callback cuya implementación es pequeña y se usa en exactamente un lugar:

button.addClickListener(new ClickListener() {
  @Override
  public void onClick() {
    System.out.println("clicked");
  }
});

En Java moderno, normalmente escribirías esto como una lambda:

button.addClickListener(() -> System.out.println("clicked"));

…y la lambda es más corta, más fácil de leer y no retiene una referencia a la clase externa. Entonces, ¿cuándo sigue teniendo sentido la forma anónima?

  • Múltiples métodos. Una lambda implementa un único método abstracto. Si necesitas sobrescribir dos métodos en una clase abstracta o implementar dos en una interfaz, solo una clase anónima servirá.
  • Subclasificar una clase concreta. Las lambdas solo apuntan a interfaces funcionales. Para sobrescribir un método en ArrayList, HashMap o tu propia clase concreta sobre la marcha, necesitas una clase anónima.
  • Llamar a super.method(...) en la sobrescritura. Las lambdas no tienen super. Las clases anónimas sí.
  • Bloques de inicialización. Las clases anónimas pueden tener bloques de inicialización de instancia ({ ... }); las lambdas no.

En la práctica, ese es un conjunto pequeño de casos. La mayoría de los callbacks modernos usan lambdas.

Captura de variables

Una clase anónima declarada dentro de un método puede leer las variables locales del método circundante — pero solo si son final o efectivamente finales (nunca reasignadas después de su inicialización):

void schedule() {
  String msg = "hello";
  Runnable r = new Runnable() {
    @Override public void run() {
      System.out.println(msg);     // captures msg
    }
  };
  r.run();
  // msg = "bye";          // would make msg no longer effectively final → ERROR above
}

La misma regla se aplica a las lambdas — es una propiedad del código circundante, no de la forma sintáctica. La razón es que el valor capturado se copia en los campos de la clase sintética; si msg pudiera cambiar después, la copia capturada quedaría silenciosamente desactualizada.

Tienen un this externo

Al igual que las clases internas, las clases anónimas declaradas en un contexto no estático llevan una referencia a la instancia envolvente. Eso significa que this dentro de la clase anónima se refiere a la instancia anónima, no a la externa. Para acceder a la instancia externa, califica con Outer.this:

public class Server {
  String name = "outer";

  Runnable handler() {
    return new Runnable() {
      String name = "inner";
      public void run() {
        System.out.println(name);            // "inner"
        System.out.println(Server.this.name);// "outer"
      }
    };
  }
}

Y la misma advertencia sobre la referencia externa que con las clases internas: devolver una instancia anónima a código de larga duración mantiene viva la instancia externa.

Limitaciones

  • Una clase anónima puede extender una superclase o implementar una interfaz — no ambas, y no más de una de cada tipo.
  • No puede tener un constructor propio — no hay nombre para darle al constructor. Puedes usar un bloque de inicialización de instancia ({ ... }) para la configuración.
  • No puede ser static.

Comparación con lambdas — la comparación en código

El mismo trabajo, de dos maneras:

// Anonymous class — older style
Comparator<String> byLength = new Comparator<String>() {
  @Override
  public int compare(String a, String b) {
    return Integer.compare(a.length(), b.length());
  }
};

// Lambda — modern equivalent
Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());

Ambos producen un Comparator<String>. La lambda es más corta y tiene semánticas de this/alcance ligeramente diferentes (sin referencia a la clase externa para contextos de instancia; this dentro de una lambda es la instancia envolvente, no un objeto nuevo). Si ambas formas funcionan para tu caso de uso, prefiere la lambda.

Un ejemplo práctico

java— editable, runs on the server

Qué sigue

El tipo de clase anidada restante es la clase local — una clase declarada dentro del cuerpo de un método, con un nombre real pero un alcance pequeño. Se superponen con las clases anónimas (y con las lambdas), pero ocasionalmente son la opción más limpia. Continúa en clases locales.

Práctica

Práctica
En Java moderno, ¿qué tarea sigue requiriendo una clase anónima en lugar de una lambda?
En Java moderno, ¿qué tarea sigue requiriendo una clase anónima en lugar de una lambda?
Was this page helpful?