Cours anonymes à Java

1. Introduction

Dans ce didacticiel, nous examinerons les classes anonymes en Java.

Nous décrirons comment nous pouvons en déclarer et en créer des instances. Nous discuterons également brièvement de leurs propriétés et de leurs limites.

2. Déclaration de classe anonyme

Les classes anonymes sont des classes internes sans nom. Comme ils n'ont pas de nom, nous ne pouvons pas les utiliser pour créer des instances de classes anonymes. En conséquence, nous devons déclarer et instancier des classes anonymes dans une seule expression au point d'utilisation.

Nous pouvons soit étendre une classe existante, soit implémenter une interface.

2.1. Prolonger une classe

Lorsque nous instancions une classe anonyme à partir d'une classe existante, nous utilisons la syntaxe suivante:

Entre parenthèses, nous spécifions les paramètres requis par le constructeur de la classe que nous étendons:

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

Naturellement, si le constructeur de la classe parent n'accepte aucun argument, nous devons laisser les parenthèses vides.

2.2. Implémenter une interface

Nous pouvons également instancier une classe anonyme à partir d'une interface:

De toute évidence, les interfaces de Java n'ont pas de constructeur, donc les parenthèses restent toujours vides. C'est la seule façon dont nous devrions le faire pour implémenter les méthodes de l'interface:

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

Une fois que nous avons instancié une classe anonyme, nous pouvons assigner cette instance à une variable afin de pouvoir la référencer quelque part plus tard.

Nous pouvons le faire en utilisant la syntaxe standard des expressions Java:

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

Comme nous l'avons déjà mentionné, une déclaration de classe anonyme est une expression, elle doit donc faire partie d'une instruction . Ceci explique pourquoi nous avons mis un point-virgule à la fin de l'instruction.

Évidemment, nous pouvons éviter d'assigner l'instance à une variable si nous créons cette instance en ligne:

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

Nous devons utiliser cette syntaxe avec beaucoup de précaution car elle pourrait facilement compromettre la lisibilité du code, surtout lorsque l'implémentation de la méthode run () prend beaucoup de place.

3. Propriétés de classe anonyme

L'utilisation de classes anonymes présente certaines particularités par rapport aux classes de premier niveau habituelles. Nous abordons ici brièvement les problèmes les plus pratiques. Pour les informations les plus précises et mises à jour, nous pouvons toujours consulter la spécification du langage Java.

3.1. Constructeur

La syntaxe des classes anonymes ne nous permet pas de leur faire implémenter plusieurs interfaces. Pendant la construction, il peut exister exactement une instance d'une classe anonyme . Par conséquent, ils ne peuvent jamais être abstraits. Puisqu'ils n'ont pas de nom, nous ne pouvons pas les prolonger. Pour la même raison, les classes anonymes ne peuvent pas avoir de constructeurs explicitement déclarés.

En fait, l'absence de constructeur ne nous pose aucun problème pour les raisons suivantes:

  1. nous créons des instances de classe anonymes au même moment que nous les déclarons
  2. à partir d'instances de classe anonymes, nous pouvons accéder aux variables locales et aux membres de la classe englobant

3.2. Membres statiques

Les classes anonymes ne peuvent pas avoir de membres statiques à l'exception de ceux qui sont constants.

Par exemple, cela ne compilera pas:

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

Au lieu de cela, nous obtiendrons l'erreur suivante:

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

3.3. Scope of Variables

Anonymous classes capture local variables that are in the scope of the block in which we have declared the class:

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

As we see, the local variables count and action are defined in the same block. For this reason, we can access count from within the class declaration.

Note that in order to be able to use local variables, they must be effectively final. Since JDK 8, it is not required anymore that we declare variables with the keyword final. Nevertheless, those variables must be final. Otherwise, we get a compilation error:

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

In order the compiler decides that a variable is, in fact, immutable, in the code, there should be only one place in which we assign a value to it. We might find more information about effectively final variables in our article “Why Do Local Variables Used in Lambdas Have to Be Final or Effectively Final?”

Let us just mention that as every inner class, an anonymous class can access all members of its enclosing class.

4. Anonymous Class Use Cases

There might be a big variety of applications of anonymous classes. Let's explore some possible use cases.

4.1. Class Hierarchy and Encapsulation

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

Les classes anonymes que nous avons considérées ci-dessus ne sont qu'un cas particulier de classes imbriquées. Généralement, une classe imbriquée est une classe déclarée dans une autre classe ou interface :

En regardant le diagramme, nous voyons que les classes anonymes ainsi que les classes membres locales et non statiques forment ce que l'on appelle les classes internes . Avec les classes membres statiques , ils forment les classes imbriquées.

6. Conclusion

Dans cet article, nous avons examiné divers aspects des classes anonymes Java. Nous avons également décrit une hiérarchie générale des classes imbriquées.

Comme toujours, le code complet est disponible dans notre référentiel GitHub.