Clases anónimas en Java

1. Introducción

En este tutorial, consideraremos clases anónimas en Java.

Describiremos cómo podemos declarar y crear instancias de ellos. También discutiremos brevemente sus propiedades y limitaciones.

2. Declaración de clase anónima

Las clases anónimas son clases internas sin nombre. Como no tienen nombre, no podemos usarlos para crear instancias de clases anónimas. Como resultado, tenemos que declarar y crear instancias de clases anónimas en una sola expresión en el punto de uso.

Podemos extender una clase existente o implementar una interfaz.

2.1. Ampliar una clase

Cuando creamos una instancia de una clase anónima a partir de una existente, usamos la siguiente sintaxis:

Entre paréntesis, especificamos los parámetros que requiere el constructor de la clase que estamos ampliando:

new Book("Design Patterns") { @Override public String description() { return "Famous GoF book."; } }

Naturalmente, si el constructor de la clase padre no acepta argumentos, deberíamos dejar los paréntesis vacíos.

2.2. Implementar una interfaz

También podemos crear una instancia de una clase anónima desde una interfaz:

Obviamente, las interfaces de Java no tienen constructores, por lo que los paréntesis siempre permanecen vacíos. Esta es la única forma en que deberíamos hacerlo para implementar los métodos de la interfaz:

new Runnable() { @Override public void run() { ... } }

Una vez que hemos creado una instancia de una clase anónima, podemos asignar esa instancia a una variable para poder hacer referencia a ella en algún lugar más adelante.

Podemos hacer esto usando la sintaxis estándar para expresiones Java:

Runnable action = new Runnable() { @Override public void run() { ... } };

Como ya mencionamos, una declaración de clase anónima es una expresión, por lo tanto, debe ser parte de una declaración . Esto explica por qué hemos puesto un punto y coma al final de la declaración.

Obviamente, podemos evitar asignar la instancia a una variable si creamos esa instancia en línea:

List actions = new ArrayList(); actions.add(new Runnable() { @Override public void run() { ... } });

Debemos usar esta sintaxis con mucho cuidado, ya que podría sufrir fácilmente la legibilidad del código, especialmente cuando la implementación del método run () ocupa mucho espacio.

3. Propiedades de clase anónimas

Existen ciertas particularidades en el uso de clases anónimas con respecto a las clases habituales de nivel superior. Aquí tocamos brevemente los temas más prácticos. Para obtener la información más precisa y actualizada, siempre podemos consultar la Especificación del lenguaje Java.

3.1. Constructor

La sintaxis de las clases anónimas no nos permite hacer que implementen múltiples interfaces. Durante la construcción, puede existir exactamente una instancia de una clase anónima . Por lo tanto, nunca pueden ser abstractos. Como no tienen nombre, no podemos extenderlos. Por la misma razón, las clases anónimas no pueden tener constructores declarados explícitamente.

De hecho, la ausencia de un constructor no representa ningún problema para nosotros por las siguientes razones:

  1. Creamos instancias de clases anónimas en el mismo momento en que las declaramos.
  2. desde instancias de clases anónimas, podemos acceder a variables locales y adjuntar a los miembros de la clase

3.2. Miembros estáticos

Las clases anónimas no pueden tener miembros estáticos excepto aquellos que son constantes.

Por ejemplo, esto no se compilará:

new Runnable() { static final int x = 0; static int y = 0; // compilation error! @Override public void run() {...} };

En su lugar, obtendremos el siguiente error:

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

3.3. Alcance de las variables

Las clases anónimas capturan variables locales que están en el alcance del bloque en el que hemos declarado la clase:

int count = 1; Runnable action = new Runnable() { @Override public void run() { System.out.println("Runnable with captured variables: " + count); } }; 

Como vemos, las variables locales cuenta y acción se definen en el mismo bloque. Por esta razón, podemos acceder a count desde dentro de la declaración de clase.

Tenga en cuenta que para poder utilizar variables locales, deben ser efectivamente finales. Desde JDK 8, ya no es necesario que declaremos variables con la palabra clave final . Sin embargo, esas variables deben ser definitivas . De lo contrario, obtenemos un error de compilación:

[ERROR] local variables referenced from an inner class must be final or effectively final

Para que el compilador decida que una variable es, de hecho, inmutable en el código, debe haber un solo lugar en el que le asignemos un valor. Podríamos encontrar más información sobre las variables efectivamente finales en nuestro artículo "¿Por qué las variables locales utilizadas en lambdas tienen que ser definitivas o efectivamente finales?"

Mencionemos que, como toda clase interna, una clase anónima puede acceder a todos los miembros de su clase adjunta .

4. Casos de uso de clases anónimas

Puede haber una gran variedad de aplicaciones de clases anónimas. Exploremos algunos casos de uso posibles.

4.1. Encapsulación y jerarquía de clases

We should use inner classes in general use cases and anonymous ones in very specific ones in order to achieve a cleaner hierarchy of classes in our application. When using inner classes, we may achieve a finer encapsulation of the enclosing class's data. If we define the inner class functionality in a top-level class, then the enclosing class should have public or package visibility of some of its members. Naturally, there are situations when it is not very appreciated or even accepted.

4.2. Cleaner Project Structure

We usually use anonymous classes when we have to modify on the fly the implementation of methods of some classes. In this case, we can avoid adding new *.java files to the project in order to define top-level classes. This is especially true if that top-level class would be used just one time.

4.3. UI Event Listeners

In applications with a graphical interface, the most common use case of anonymous classes is to create various event listeners. For example, in the following snippet:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ... } }

we create an instance of an anonymous class that implements interface ActionListener. Its actionPerformed method gets triggered when a user clicks the button.

Since Java 8, lambda expressions seem to be a more preferred way though.

5. General Picture

Las clases anónimas que consideramos anteriormente son solo un caso particular de clases anidadas. Generalmente, una clase anidada es una clase que se declara dentro de otra clase o interfaz :

Al observar el diagrama, vemos que las clases anónimas junto con las de miembros locales y no estáticos forman las llamadas clases internas . Junto con las clases de miembros estáticos , forman las clases anidadas.

6. Conclusión

En este artículo, hemos considerado varios aspectos de las clases anónimas de Java. También hemos descrito una jerarquía general de clases anidadas.

Como siempre, el código completo está disponible en nuestro repositorio de GitHub.