|
Les classes internes |
Les classes internes
Les classes anonymes
Autres nouveautés Java 1.1
Ce chapitre décrit les ajouts apportés dans le langage Java à partir de Java 1.1, et particulièrement les classes internes (inner classes).
Syntaxe
Avec Java 1.0, il n'est possible de créer des classes public ou non qu'au niveau le plus haut dans un fichier .java, c'est à dire des classes externes dépendant directement d'un package. Java 1.1 introduit la possibilité de créer des classes internes ou interfaces internes qui sont déclarées à l'intérieur d'une classe ou d'une interface, en plus des méthodes et des champs :
class ClasseExterne { // Déclararation d'une classe interne ModificateurClasseInterne class ClasseInterne { // Corps de ClasseInterne : // Déclaration des champs, des méthodes, des constructeurs,... } // Déclaration d'une classe interne dérivant d'une super classe // et implémentant une interface ModificateurClasseInterne class ClasseInterne2 extends nomDeSuperClasse implements nomInterface //, nomInterface2, ... { // ... } // Déclararation d'une interface interne ModificateurInterfaceInterne interface InterfaceInterne { } // Autres déclarations }ModificateurClasseInterne est optionnel et peut être un ou plusieurs des mots-clés suivants :
- public, protected ou private : Ces mots clés indiquent le modificateur d'accès de la classe interne et ont le même sens que pour la déclaration des champs d'une classe.
- final : Comme pour une classe externe, une classe interne déclarée final ne peut être dérivée.
- abstract : Comme pour une classe externe, il est impossible de créer une instance d'une classe interne déclarée abstract.
- static : Les classes internes déclarées sont très différentes si elles sont déclarées static ou non.
Comme pour les champs static, une classe interne static ou ses instances ne dépend d'aucune instance de la classe externe dans laquelle est elle est définie. Dans ce cas, il est possible de créer une instance d'une classe interne simplement par new ClasseExterne.ClasseInterne () par exemple.
L'instance d'une classe interne non static stocke automatiquement une référence vers l'instance de la classe externe ClasseExterne dont elle dépend, ce qui permet d'utiliser directement toutes les méthodes et les champs de la classe ClasseExterne. Cette référence doit être donnée à la création d'un objet de cette classe interne comme par exemple objetClasseExterne.new ClasseInterne () pour créer un objet de classe ClasseInterne dépendant de l'objet objetClasseExterne de classe ClasseExterne.ModificateurInterfaceInterne peut prendre toutes les valeurs que ModificateurClasseInterne, sauf final (abstract est implicite).
Une classe interne non static ne peut pas déclarer des champs et des méthodes static.De même dans un bloc, il est possible de déclarer des classes internes locales dont la portée est limitée au bloc. Dans ce cas, ModificateurClasseInterne ne peut être prendre comme valeur que final ou abstract.
Une classe interne peut déclarer elle-même d'autres classes internes.
Pour chacune des classes internes déclarées est généré un fichier .class à la compilation. Pour assurer l'unicité du nom de ces fichiers, la syntaxe suivante est utilisée : La classe interne ClasseInterne déclarée à l'intérieur d'une classe externe ClasseExterne, sera stockée dans le fichier ClasseExterne$ClasseInterne.class.
Pour les classes internes déclarées dans un bloc, le nom de fichier comporte en plus un identifiant numérique généré par le compilateur donnant comme nom de fichier par exemple ClasseExterne$1$ClasseInterne.class.
Java 1.1 permet de déclarer des classes internes (inner classes). Les classes internes static correspondent aux classes internes du C++ (nested classes). Par contre les classes internes non static sont un concept inexistant en C++ et permettent aux instances de ces classes de garder implicitement un lien avec l'instance de la classe externe dont elles dépendent.
Le mécanisme utilisé par le compilateur Java 1.1 pour générer les classes internes est entièrement compatible avec la Machine Virtuelle Java 1.0.
Donc même si vous faites fonctionner vos applets avec Java 1.0, vous pouvez leur permettre quand même d'utiliser les classes internes en les compilant avec un compilateur Java 1.1 en donnant comme classpath la librairie des classes de Java 1.0.Utilisation
Bien qu'il ne soit pas obligatoire de s'en servir, les classes internes apportent un plus pour l'organisation et la programmation des classes de votre programme :
|
|
import java.applet.Applet; import java.awt.*; public class AfficheurDeCalcul extends Applet { private Thread calculateur; private Thread afficheur; public void start () { setBackground (Color.white); // Création de deux instances des classes // Calculateur et Afficheur calculateur = new Calculateur (this); afficheur = new Afficheur (this); calculateur.start (); afficheur.start (); } // ... // Les méthodes stop (), calculerCourbe () // dessinerCourbe () et paint () // sont inchangées } // Fin de AfficheurDeCalcul // Classe friendly dérivant de Thread class Calculateur extends Thread { private AfficheurDeCalcul applet; public Calculateur (AfficheurDeCalcul applet) { this.applet = applet; } public void run () { while (isAlive ()) applet.calculerCourbe (); } } // Classe friendly dérivant de Thread class Afficheur extends Thread { private AfficheurDeCalcul applet; public Afficheur (AfficheurDeCalcul applet) { this.applet = applet; } public void run () { while (isAlive ()) applet.dessinerCourbe (); } } |
import java.applet.Applet; import java.awt.*; public class AfficheurDeCalcul extends Applet { private Thread calculateur; private Thread afficheur; public void start () { setBackground (Color.white); // Création de deux instances des classes // internes Calculateur et Afficheur calculateur = new Calculateur (); afficheur = new Afficheur (); calculateur.start (); afficheur.start (); } // ... // Les méthodes stop (), calculerCourbe () // dessinerCourbe () et paint () // sont inchangées // Classe interne dérivant de Thread private class Calculateur extends Thread { public void run () { while (isAlive ()) // calculerCourbe (), méthode de // la classe externe AfficheurDeCalcul // peut être appelée directement calculerCourbe (); } } // Classe interne dérivant de Thread private class Afficheur extends Thread { public void run () { while (isAlive ()) // dessinerCourbe (), méthode de // la classe externe AfficheurDeCalcul // peut être appelée directement dessinerCourbe (); } } } // Fin de AfficheurDeCalcul |
Par extension des classes internes locales, vous pouvez déclarer aussi des classes "anonymes" en Java.
C'est un ajout à la syntaxe de l'opérateur new : Après l'instruction new Classe1 (), il est possible d'ajouter un bloc permettant de modifier le comportement de Classe1, en outrepassant telle ou telle méthode de Classe1.
Résultat : un objet d'une classe "anonyme" dérivée de Classe1 est créé, puis un cast implicite de cette classe vers Classe1 est effectué.SuperClasse objet = new SuperClasse (/* argument1, argument2, ...*/) { // Méthodes de SuperClasse outrepassées // pour modifier le comportement de SuperClasse };Dans la même logique, il est possible de créer une instance d'une classe anonyme implémentant une interface InterfaceX, grâce à l'instruction :
InterfaceX objet2 = new InterfaceX () { // Implémentation de toutes les méthodes de InterfaceX };Dans ce cas, le bloc qui suit new InterfaceX () doit implémenter toutes les méthodes de InterfaceX pour qu'il soit possible de créer une instance d'une telle classe.
Comme toute classe interne, une classe anonyme peut déclarer un ensemble de champs et de méthodes d'instances.Pour chacune des classes anonymes déclarées est généré un fichier .class à la compilation. Pour assurer l'unicité du nom de ces fichiers, le nom de chaque fichier est constitué du nom de la classe externe suivi du symbole $ et d'un identifiant numérique généré par le compilateur, comme par exemple ClasseExterne$1.class.
Bien que les classes anonymes peuvent en apparence obscurcir la lisibilité d'un programme, il existe un ensemble de circonstances où il est intéressant de les utiliser :
- Pour créer une instance d'un objet d'une classe dont on veut outrepasser juste une ou deux méthodes.
- Implémenter localement une interface telle qu'un listener. Ce type d'interface est utilisé dans la gestion de l'Interface Utilisateur AWT à partir de Java 1.1 pour déclarer les méthodes qu'une classe doit implémenter pour être rappelées quand un événement survient.
Par exemple, il est possible d'encore simplifier l'applet AfficheurDeCalcul, en remplaçant les classes internes Calculateur et Afficheur par des classes anonymes :
import java.applet.Applet; import java.awt.*; public class AfficheurDeCalcul extends Applet { private Thread calculateur; private Thread afficheur; public void start () { setBackground (Color.white); // Création de deux instances de classes // anonymes implémentant la méthode run () // de la classe Thread calculateur = new Thread () { public void run () { while (isAlive ()) // calculerCourbe (), méthode de // la classe externe AfficheurDeCalcul // peut être appelée directement calculerCourbe (); } }; afficheur = new Thread () { public void run () { while (isAlive ()) // dessinerCourbe (), méthode de // la classe externe AfficheurDeCalcul // peut être appelée directement dessinerCourbe (); } }; calculateur.start (); afficheur.start (); } // ... // Les méthodes stop (), calculerCourbe () // dessinerCourbe () et paint () // sont inchangées }
Les classes anonymes permettent de transformer facilement un programme existant pour exécuter un bloc d'instructions dans un thread isolé en ajoutant sur place les quelques instructions suivantes :
Avant
Après // ... { // Bloc d'instructions } // ... new Thread () { public void run () { // Bloc d'instructions } }.start ();
Pour éviter toute confusion avec le reste des instructions, utilisez des règles d'écriture et une indentation claires pour l'écriture des classes anonymes.
Initialisations d'instance
En plus des initialisations static, il est possible d'ajouter à une classe des blocs d'initialisations d'instance. Ces blocs d'instructions sont exécutés à la création d'un objet juste après le constructeur de sa super classe et avant tout constructeur de sa classe.
Ces initialisations sont surtout utiles pour les classes anonymes qui ne peuvent pas déclarer de constructeurs.Sauf pour les classes anonymes, les blocs d'initialisations d'instance d'une classe Classe1 ne peuvent déclencher d'exceptions que si tous les constructeurs de Classe1 déclarent qu'ils sont susceptibles de déclencher ces classe d'exceptions avec la clause throws.
Initialisation de tableaux
Les tableaux peuvent être initialisés à leur création en faisant suivre l'instruction de création du tableau new Classe0 [], par la liste des éléments à stocker, comme dans l'exemple suivant :
class Classe1 { // Les deux instructions suivantes sont équivalentes int [ ] tab1 = {1, 2}; int [ ] tab2 = new int [] {1, 2}; void methode1 (String [] tab) { } void methode2 () { // Possibilité de créer des tableaux, envoyés // directement en paramètre à une méthode methode1 (new String [] {"valeur1", "valeur2"}); } }Comme vous pouvez le voir, c'est surtout pratique pour envoyer un tableau en paramètre sans avoir à le déclarer dans une instruction séparée.
Utilisation du mot-clé class
Toute classe ou interface peut être suivie du mot-clé class : ceci produit le même effet que l'utilisation de la méthode forName () de la classe Class.
L'instruction String.class équivalente à Class.forName ("java.lang.String") est bien plus pratique à utiliser car vous n'êtes pas obligé de donner le package complet de la classe String et d'intercepter l'exception ClassNotFoundException que peut déclencher la méthode forName ().Cette nouvelle syntaxe peut être aussi utilisée pour tous les types primitifs et void, de la manière suivante :
byte.class short.class int.class long.class float.class double.class char.class boolean.class void.classCeci est utilisé en particulier par les classes du package java.lang.reflect pour manipuler tous les types Java (que ce soient des classes ou des types primitifs) sous forme d'un objet de classe Class.
Variables locales et paramètres final
Les variables locales et les paramètres d'une méthode ou d'un constructeur peuvent être déclarés final.
TypeRetour methode1 (final TypeParam1 param1Name /*,... */) { final TypeVariable variableLocale1 = valeur; // ... }Il n'est pas obligatoire d'initialiser une variable locale final dès sa déclaration, mais par contre il n'est possible de lui assigner qu'une seule fois une valeur.
Tout paramètre ou toute variable locale que vous voulez utiliser dans une classe anonyme doivent être déclarés final. Ceci permet à cette classe anonyme d'utiliser ces variables temporaires sans risque qu'elles soient modifiées ultérieurement.
Par contre, tous les champs d'instance ou de classe existent de façon permanente et peuvent être utilisées dans une classe anonyme qu'elles soient final ou non.
Comme avec const en C/C++, les paramètres d'une méthode peuvent être déclarés constants grâce à final en Java. Mais ceci interdit uniquement à une méthode de modifier la valeur d'un paramètre. Si un paramètre param1 final est une référence sur un objet, il est impossible de modifier param1 mais l'objet désigné par param1 lui peut être modifié.
|