Informatique / Programmation Programmation orientée objet avec Java 14 : Classes internes Jacques Bapst [email protected] Java / Classes internes Classes internes Jusqu'à présent, les classes (et interfaces) que nous avons vues étaient toutes des classes (et interfaces) de premier niveau (c'està-dire des membres directs des paquetages, sans imbrication). Le langage Java permet cependant de définir des classes à l'intérieur d'autres classes. On parle dans ce cas de classes internes (Nested Class). Il existe quatre types de classes internes : Les classes membres statiques Les classes membres Les classes locales Les classes anonymes (Static Nested Class) (Inner Class) (Local Inner Class) (Anonymous Inner Class) Attention : D'un auteur à l'autre, le vocabulaire utilisé pour qualifier ces différentes classes internes peut varier sensiblement. EIA-FR / Jacques Bapst PR1_14 2 Java / Classes internes Classes membres statiques [1] Une classe membre statique est une classe définie comme un membre statique d'une autre classe. Des interfaces peuvent également être déclarées comme membres statiques d'une classe. Les remarques qui suivent s'appliquent donc également aux interfaces membres statiques. Par analogie avec les méthodes statiques, on pourrait parler de "classe de classe" (mais cela porte un peu à confusion). Une classe membre statique se comporte comme une classe de premier niveau avec en outre la possibilité d'accéder aux membres statiques de la classe qui la contient (classe englobante). Une classe membre statique a accès à tous les membres statiques de sa classe englobante, y compris les membres privés (private). L'inverse est également vrai : les méthodes de la classe englobante ont accès à tous les membres (statiques ou non-statiques) d'une classe membre statique, y compris les membres privés. EIA-FR / Jacques Bapst PR1_14 3 Java / Classes internes Classes membres statiques [2] Une classe membre statique a même accès à tous les membres des autres classes membres statiques (classes "sœurs"), y compris aux membres déclarés private de ces classes. Une classe membre statique peut être déclarée avec ses propres modificateurs de contrôle d'accès. Ces modificateurs (public, protected, private, aucun) possèdent les mêmes significations que pour les autres membres d'une classe (champs et méthodes). En dehors de la classe englobante, une classe membre statique est nommée en combinant le nom de la classe externe avec le nom de la classe interne. Par exemple : LinkedStack.Linkable node Restrictions : • Une classe membre statique ne peut pas posséder le même nom qu'une de ses classes englobantes. • Une classe membre statique ne peut être englobée que dans une classe de premier niveau ou dans une classe membre statique. EIA-FR / Jacques Bapst PR1_14 4 Java / Classes internes Exemple d'interface membre statique //===== Une classe qui implémente une pile sous forme de liste chaînée ===== public class LinkedStack { // Interface membre statique qui définit la manière dont les objets sont liés public static interface Linkable { public Linkable getNext(); public void setNext(Linkable node); } // La tête de liste est un objet de type Linkable Linkable head; // Les corps des méthodes sont omis public void push(Linkable node) { ... } public Linkable pop() { ... } } //===== Cette classe implémente l'interface membre statique ===== public class LinkableInteger implements LinkedStack.Linkable { // Les données et le constructeur du noeud int i; public LinkableInteger(int i) { this.i = i; } // Données et méthodes requises pour implémenter l'interface LinkedStack.Linkable next; public LinkedStack.Linkable getNext() { return next; } public void setNext(LinkedStack.Linkable node) { next = node; } } EIA-FR / Jacques Bapst PR1_14 5 Java / Classes internes Classes membres [1] Une classe membre est une classe qui est déclarée comme un membre non-statique d'une classe englobante. Les interfaces ne peuvent pas être déclarées comme membres non-statiques d'une classe. Une instance d'une classe membre est toujours associée à une instance (un objet) de la classe englobante. Le code d'une classe membre a accès à tous les champs et à toutes les méthodes (autant statiques que non-statiques et même s'ils sont déclarés privés) de sa classe englobante et des classes "sœurs". Comme tous les autres membres de la classe, une classe membre peut être déclarée avec ses propres modificateurs de contrôle d'accès. Ces modificateurs (public, protected, private, aucun) possèdent les mêmes significations que pour les autres membres de la classe (champs et méthodes). EIA-FR / Jacques Bapst PR1_14 6 Java / Classes internes Classes membres [2] Restrictions : • Une classe membre ne peut pas porter le même nom qu'une classe ou qu'un paquetage englobant (une telle restriction ne s'applique pas aux champs et aux méthodes). • Les classes membres ne peuvent pas contenir de champs, de méthodes ou de classe déclarés static (à l'exception des constantes qui sont déclarées à la fois static et final). Pour instancier une classe membre à l'extérieur de la classe de définition, il faut disposer d'une instance de la classe englobante. On utilisera cette instance avec l'opérateur new pour créer un objet de la classe membre. Syntaxe : InstanceExt.new ClasseInterne Exemple : ExtClName eObj = new ExtClName(…); ExtClName.IntClName iObj = eObj.new IntClName(…); // Objet externe // Objet interne et non pas : ExtClName.IntClName iObj = eObj.new ExtClName.IntClName(…); EIA-FR / Jacques Bapst PR1_14 7 Java / Classes internes Classes membres [3] Complément Une syntaxe particulière est utilisée pour accéder explicitement aux champs et méthodes d'instance de son objet englobant. Cette syntaxe n'est nécessaire que pour référencer un membre d'une classe englobante qui est masqué par un membre du même nom dans la classe interne (ce qui est à éviter !) : Nom_Classe_Englobante . this . Nom_Membre LinkedStack.this.head Il est également possible d'accéder à un champ masqué ou à une méthode redéfinie d'une super-classe de la classe englobante : Nom_Classe_Englobante . super . Nom_Membre Reactor.super.run() Une classe de premier niveau peut étendre (spécialiser) une classe membre ce qui introduit deux hiérarchies distinctes (la hiérarchie de classes habituelle + une hiérarchie de confinement) qui affectent les éléments visibles (portée). A éviter ! EIA-FR / Jacques Bapst PR1_14 8 Java / Classes internes Exemple de classe membre //===== Une classe qui implémente une pile sous forme de liste chaînée ===== public class LinkedStack { // Interface membre statique qui définit la manière dont les objets sont liés public static interface Linkable { ... } // Contenu omis private Linkable head; public void push(Linkable node) { ... } public Linkable pop() { ... } // Contenu omis // Contenu omis // Cette méthode retourne un objet de type Enumeration pour l'objet LinkedStack courant public java.util.Enumeration enumerate() { return new Enumerator(); } // Classe membre qui implémente l'interface Enumeration protected class Enumerator implements java.util.Enumeration { Linkable current; // Le constructeur utilise le champ privé head de la classe englobante public Enumerator() { current = head; } public boolean hasMoreElements() { return (current != null); } public Object nextElement() { if (current==null) throw new java.util.NoSuchElementException(); Object value = current; current = current.getNext(); return value; } } } EIA-FR / Jacques Bapst PR1_14 9 Java / Classes internes Classes locales [1] Une classe locale est une classe qui est déclarée au sein d'un bloc de code Java (généralement dans une méthode). Tout comme une variable locale, une classe locale n'est visible qu'à l'intérieur du bloc dans lequel elle a été déclarée. Les classes locales sont déclarée à l'intérieur d'une classe englobante et elles partagent de ce fait plusieurs caractéristiques des classes membres. Les interfaces ne peuvent pas être déclarées localement. Le code d'une classe locale peut accéder à tous les membres (y compris aux membres privés) de la classe englobante. Les classes locales peuvent également accéder à toutes les variables locales et aux paramètres de méthode qui se trouvent dans la portée de la définition locale de la méthode pour autant qu'ils soient effectivement final (avec ou sans le modificateur) (car la durée de vie d'une instance d'une classe locale peut être plus longue que l'exécution de la méthode dans laquelle elle est déclarée). EIA-FR / Jacques Bapst PR1_14 10 Java / Classes internes Classes locales [2] Les restrictions suivantes s'appliquent aux classes locales : • Une classe locale n'est visible qu'au sein du bloc qui la définit; elle ne peut jamais être utilisée hors de ce bloc. • Les classes locales ne peuvent pas être déclarées public, protected, private ou static. Ces modificateurs sont réservés aux membres de la classe. • Pour les mêmes raisons que les classes membres, les classes locales ne peuvent pas contenir de champs, de méthodes ou de classes statiques (à l'exception des constantes déclarées static final). • Une classe locale ne peut pas avoir le même nom qu'une de ses classes englobantes. • Une classe locale ne peut accéder aux variables locales et paramètres de méthode qui se trouvent dans sa portée que si ces variables et paramètres sont effectivement final (avec ou sans le modificateur final). Dans ce cas, le compilateur créera une copie de ces variables locales car leur durée de vie peut être plus courte que celle des instances de la classe locale. EIA-FR / Jacques Bapst PR1_14 11 Java / Classes internes Exemple de classe locale //===== Un exemple de classe locale utilisant des variables locales ===== public class TestLocalClass { // Interface membre statique public static interface IntHolder { public int getValue(); } public static void main(String[] args) { IntHolder[] holders = new IntHolder[10]; for (int i=0; i<10; i++) { final int fi = i; // Classe locale pouvant utiliser les variables locales déclarées (ou effectivement) final class MyIntHolder implements IntHolder { public int getValue() { return fi; } } holders[i] = new MyIntHolder(); // Instancie la classe locale } // La classe locale et la variable fi ne sont plus visibles mais on peut malgré tout utiliser // le tableau d'instance holders et afficher les valeurs 0…9 (1) for (int i=0; i<10; i++) System.out.println(holders[i].getValue()); } } (1) Pour expliquer ce comportement assez surprenant, il faut savoir que chaque instance d'une classe locale possède une copie privée de chaque variable locale effectivement final qu'elle utilise. Ainsi, l'instance mémorise l'état des variables qui étaient dans sa portée au moment où elle a été créée. EIA-FR / Jacques Bapst PR1_14 12 Java / Classes internes Classes anonymes [1] Une classe anonyme est une classe locale qui ne possède pas de nom. Une classe anonyme est soit : • une sous-classe d'une classe parente existante • une classe qui implémente une interface existante (c'est donc également une sous-classe d'Object) Une classe anonyme combine la déclaration et l'instanciation de la sous-classe sous la forme d'une expression qui peut apparaître comme fragment d'une expression plus grande (par exemple dans une invocation de méthode). Lorsqu'une sous-classe locale (ou classe implémentant une interface) n'est utilisée qu'une seule fois, on peut envisager d'en faire une classe anonyme qui place la définition et l'utilisation de la classe au même endroit. Cela peut, dans certains cas, favoriser la compacité et la lisibilité du code source. EIA-FR / Jacques Bapst PR1_14 13 Java / Classes internes Classes anonymes [2] La création d'une classe anonyme fait intervenir une nouvelle syntaxe qui combine la définition et l'instanciation de la classe. Pour la définir plus formellement, il faut distinguer les deux situations possibles. Sous-classe anonyme : new Nom_Classe_Parente( [ Liste_Param ] ) { Corps_Sous_Classe } Classe anonyme implémentant une interface : new Nom_Interface() { Corps_Classe } Cette nouvelle syntaxe introduit une complication supplémentaire sur le plan de la mise en page (indentation) car la création d'une classe anonyme s'effectue généralement dans le cadre d'une expression (voir exemple). A utiliser avec modération ! EIA-FR / Jacques Bapst PR1_14 14 Java / Classes internes Classes anonymes [3] Les classes anonymes constituent une forme particulière de classes locales et toutes les restrictions mentionnées pour les classes locales s'appliquent également aux classes anonymes. En plus, on peut mentionner la restriction suivante : • Comme les classes anonymes ne possèdent pas de nom, il n'est pas possible de définir explicitement des constructeurs (elles possèdent uniquement le constructeur par défaut). En remplacement, on peut cependant créer un initialiseur d'instance qui pourra jouer pratiquement le même rôle qu'un constructeur. Quand utiliser une classe anonyme ? • La classe possède un corps très court • Une seule instance de la classe est nécessaire • Le nom de la classe ne facilite pas la compréhension du code Il faut toujours privilégier la lisibilité du code source. EIA-FR / Jacques Bapst PR1_14 15 Java / Classes internes Exemple de classe anonyme //===== Un exemple de classe anonyme pour filtrer des fichiers dans un répertoire ===== public class TestAnonymousClass { // Liste tous les fichiers du répertoire D:\Data possédant l'extension .java public static void main(String[] args) { File f = new File("D:\\Data"); // Le répertoire à énumérer // La méthode list() prend en argument un objet de type FilenameFilter // Une instance de la sous-classe anonyme de FilenameFilter est créée // directement dans l'expression d'invocaton de la méthode list() String[] fileList=f.list(new FilenameFilter() { public boolean accept(File dir, String s) { return s.endsWith(".java"); } }); // N'oubliez pas la parenthèse et le point-virgule qui achèvent l'appel de la méthode // Affichage de la liste des fichiers trouvés for (int i=0; i<fileList.length; i++) { System.out.println("File name : " + fileList[i]); } } } Noter la mise en page pour l'écriture des classes anonymes (conventions de codage Oracle). EIA-FR / Jacques Bapst PR1_14 16 Java / Classes internes Remarques sur les classes internes [1] Les différents types de classes internes offrent des mécanismes supplémentaires pour organiser et structurer le code source. En créant une classe interne, on limite leur niveau d'exposition en les encapsulant au sein d'autres classes. Pour les classes membres (statiques ou non), on peut limiter leur accès en utilisant les modificateurs public, protected et private. On peut ainsi retrouver au niveau des classes internes certains bénéfices de l'encapsulation. Si l'on combine plusieurs niveaux d'imbrication (qui créent une hiérarchie de confinement) avec une hiérarchie de classes complexe, on peut très vite arriver à de grandes complications en terme de portée, visibilité et masquage des identificateurs concernés. Les classes internes ne doivent être utilisées que si elles apportent des simplifications dans la conception et favorisent la lisibilité et la maintenance du code. Sinon, elles sont plutôt à éviter. EIA-FR / Jacques Bapst PR1_14 17 Java / Classes internes Remarques sur les classes internes [2] Dans les applications comportant une interface utilisateur graphique (GUI), les classes internes et plus particulièrement les classes anonymes sont fréquemment utilisées pour créer, à la volée, des gestionnaires d'événements (Event Listener). Sur le plan de l'implémentation, les classes internes sont traitées de la même manière que les classes de premier niveau : chacune donne lieu à un fichier .class séparé (c'est également vrai pour les classes anonymes). Suivant les types de classes internes, le nom du fichier résultant de la compilation est composé de manière différente mais il comporte généralement le caractère '$' (on trouvera par exemple les fichiers LinkedStack$Linkable.class ou FilenameFilter$1.class). Lors du déploiement d'une application, il peut être important de connaître ces détails d'implémentation; par contre, pour l'écriture d'une application, cela n'a aucune incidence. EIA-FR / Jacques Bapst PR1_14 18