Une classe interne est une classe qui est définie à l'intérieur d'une autre classe. Cette particularité présente de nombreux avantages que nous allons étudier ensemble.
Java permet de définir ponctuellement une classe, sans
lui donner de nom. Cette particularité a été introduite par la version 1.1 pour
faciliter la gestion des événements. Nous la présentons succinctement ici, en
dehors de ce contexte.
Une classe est dite interne lorsque sa définition est située à l'intérieur de la définition d'une autre classe. Trois raisons justifient l'emploi de classes internes :
La grande particularité d'une classe interne, c'est qu'une de ses méthodes a accès à la fois à ses propres attributs et à ceux de l'objet externe. Ainsi, dans le code suivant, la méthode actionPerformed() qui est une méthode intégrée à la classe interne Minuteur, peut tout de même agir sur l'objet heure, qui est normalement un attribut de la classe externe Fenêtre :
package horloge; import java.awt.*; import java.awt.event.*; import java.text.DateFormat; import java.util.Date; import javax.swing.*; public class Fenêtre extends JFrame { private JLabel heure = new JLabel(); public Fenêtre() { super("Horloge"); setBounds(100, 100, 180, 80); setDefaultCloseOperation(EXIT_ON_CLOSE); heure.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 32)); heure.setHorizontalAlignment(JLabel.CENTER); add(heure); setResizable(false); setVisible(true); new Minuteur(); } public static void main(String[] args) { new Fenêtre(); } private class Minuteur implements ActionListener { private Timer minuteur = new Timer(1000, this); public Minuteur() { minuteur.start(); } public void actionPerformed(ActionEvent e) { heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date())); } } }
La classe Minuteur est une classe interne, qui plus est, privée. Seule les classes internes peuvent être privées. Il est donc impossible à l'extérieur de la classe Fenêtre d'atteindre la classe Minuteur et d'en créer une instance. Seule la classe externe conteneur a le droit de le faire, et utilise ainsi la classe interne à bon escient. Nous sommes isi en présence d'un mécanisme hautement sécurisé.
Un objet de classe interne jouit de trois propriétés particulières :
Le premier point n'apporte rien de nouveau par rapport à la situation d'objets membres, il n'en va pas de même pour les deux autres points qui permettent d'établir une communication privilégiée entre objet externe et objet interne.
C'est assez rare, mais il est possible que votre classe interne possède un attribut qui porte le même nom qu'un attribut de la classe externe. Du coup, comment atteindre cet attribut externe depuis la classe interne ? La réponse est simple. Il suffit de faire référence à this de la classe voulue par la syntaxe générale suivante :
ClasseExterne.this
En reprenant l'exemple précédent, voici comment nous pouvons également atteindre l'objet heure par cette technique :
Fenêtre.this.heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date()));
Si la classe interne n'est pas privée, vous pouvez y faire référence de l'extérieur à l'aide de la syntaxe suivante :
ClasseExterne.ClasseInterne
Pour protéger encore plus votre code source, et lorsque vous avez besoin d'une seule instance de la classe interne, il est également possible de définir les classes localement à l'intérieur d'une seule méthode.
package horloge; import java.awt.*; import java.awt.event.*; import java.text.DateFormat; import java.util.Date; import javax.swing.*; public class Fenêtre extends JFrame { private JLabel heure = new JLabel(); public Fenêtre() { super("Horloge"); setBounds(100, 100, 180, 80); setDefaultCloseOperation(EXIT_ON_CLOSE); heure.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 32)); heure.setHorizontalAlignment(JLabel.CENTER); add(heure); setResizable(false); lancerMinuteur(true); setVisible(true); } public static void main(String[] args) { new Fenêtre(); } private void lancerMinuteur(final boolean beep) { class Minuteur implements ActionListener { private Timer minuteur = new Timer(1000, this); public Minuteur() { minuteur.start(); } public void actionPerformed(ActionEvent e) { heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date())); if (beep) Toolkit.getDefaultToolkit().beep(); } } new Minuteur(); } }
Voici quelques remarques importantes sur les classes internes locales :
Le mot clé final peut être appliqué aux variables locales, aux variables d'instance et aux variables statiques. Dans tous les cas, cela signifie la même chose : cette variable ne peut être affectée qu'une seule fois après sa création. Il n'est pas possible d'en modifier ultérieurement la valeur - elle est définitive.
Lors de l'utilisation de classes internes locales, vous pouvez souvent aller plus loin. Si vous ne désirez créer qu'un seul objet de cette classe, il n'est même pas nécessaire de donner un nom à cette classe. Une telle classe est appelée classe interne anonyme :
private void lancerMinuteur(final boolean beep) { ActionListener écouteur = new ActionListener() { public void actionPerformed(ActionEvent e) { heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date())); if (beep) Toolkit.getDefaultToolkit().beep(); } }; new Timer(1000, écouteur).start(); }
Ainsi, dans la méthode lancerMinuteur(), nous créons un objet écouteur d'une classe qui implémente l'interface ActionListener, où la méthode actionPerformed() requise est celle définie entre accolades { }.
Voici les différents critères requis pour construire une classe anonyme :
new SuperType(paramètres de construction)
{
méthodes et attributs de la classe interne
}
SuperType peut être ici une interface telle que ActionListener ; la classe interne implémente alors cette interface. SuperType peut également être une classe, et dans ce cas la classe interne étend cette classe.new TypeInterface() { méthodes et attributs de la classe interne }
A titre d'exemple, je vous propose de modifier la méthode lancerMinuteur() en créant cette fois-ci le Timer dans la classe anonyme. Pour que ce dernier puisse être lancé, je prévoie alors un bloc d'initialisation à la place du constructeur puisqu'il nous est interdit d'en placer un :
private void lancerMinuteur(final boolean beep) { new ActionListener() { private Timer minuteur = new Timer(1000, this); { minuteur.start(); } public void actionPerformed(ActionEvent e) { heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date())); if (beep) Toolkit.getDefaultToolkit().beep(); } }; }
Après cette première approche, je vous porpose de passer au chapitre suivant qui est entièrement consacré aux classes anonymes.
.
Je le rappelle, une classe anonyme est une classe locale (interne) sans nom.
Une classe anonyme est définie
et instanciée dans une unique expression
concise en utilisant l'opérateur new.
Alors qu'une définition de classe locale est une instruction dans un
bloc de code Java, une définition de classe anonyme est une expression,
ce qui signifie qu'elle peut être incluse dans une expression plus grande
comme un appel de méthode.
Lorsqu'une classe locale n'est utilisée qu'une seule fois, envisagez d'utiliser la syntaxe des classes anonymes qui place la définition et l'utilisation de la classe au même endroit.
Comme vous pouvez le constater, la syntaxe de définition d'une classe anonyme et de création de cette classe utilise le mot clé new, suivi du nom de la classe et d'une définition d'un corps entre accolades { }. Si le nom suivant le mot clé new est le nom d'une classe, la classe anonyme devient une sous-classe de la classe nommée. Si le nom suivant new spécifie une interface, alors la classe anonyme implémente cette interface et devient une sous-classe de Object.
Supposons que l'on dispose d'une classe A.
Il est possible de créer un objet d'une classe dérivée de A,
en utilisant une syntaxe de cette forme :
Tout se passe comme si nous avions procédé ainsi :
Cependant, dans ce dernier cas, il serait possible de définir des références
de type A1, alors que c'est impossible dans le
premier cas.
Voici un petit programme illustrant cette possibilité. La classe A y est réduite à une seule méthode affiche(). Nous créons une classe anonyme, dérivée de A, qui redéfinit la méthode affiche().
Je suis un anonyme dérivé de A.
Notez bien que si A n'avait pas comporté de méthode affiche(), l'appel a.affiche() aurait été incorrect, compte tenu du type de la référence a (revoyez éventuellement les règles relatives au polymorphisme). Cela montre
qu'une classe anonyme ne peut pas introduire de nouvelles méthodes ; notez à
ce propos que même un appel de cette forme serait incorrect :
Il
s'agit de classes dérivées ou implémentant une interface.
.
La syntaxe de définition d'une classe anonyme ne s'applique que dans deux cas :
Voici un exemple simple de la deuxième situation :
Je suis un anonyme implementant Affichable.
Dans les précédents exemples, la référence de la classe anonyme était conservée dans une variable (d'un type de base ou d'un type interface).
Nous pouvons aussi
la transmettre en argument d'une méthode ou en valeur de retour :
L'utilisation de classes anonymes conduit généralement à
des codes peu lisibles. Nous les réserverons à des cas très
particuliers où la définition de la classe anonyme reste brève.
Les classes anonymes sont particulièrement intéressantes dans
la définition de classes écouteurs d'événements.
En voici d'ailleurs un exemple, traité d'abord sans classe anonyme, puis réalisé sous forme de classe anonyme :