Types enchassés

images/orangebelt.png

On a parfois des types qui sont étroitement liés dans leur définition.

Types membres de classe ( nested types)

Une interface java avec contrôle précis de paramètres. 

package com.maboite.ventes ;

public interface Messager {
    enum Urgence { FAIBLE, NEUTRE, ELEVEE ; } // membre implicitement "static"

   public void envoiMessage(String message, Urgence urgence)
         throws  ExceptionRoutage, ExceptionUrgence ;
}

Une utilisation :

   agent.envoiMessage(message, Messager.Urgence.ELEVEE) ;

ou

import static com.maboite.ventes.Messager.* ;
   // .....
   agent.envoiMessage(message, Urgence.ELEVEE) ;

On a ici un type Urgence qui est défini à l’intérieur d’un autre type. La compilation génére un fichier binaire distinct (qui sera Message$Urgence.class). Ce type est un membre (static) de la classe englobante.

On peut utiliser ce genre de définition quand deux types sont très couplés (non indépendants l’un de l’autre). Le type enchassé (ici "nested type" en Anglais) a l’avantage d'être en portée syntaxique du code englobant : il a donc accès aux éléments marqués private dans ce code!

Une classe statique enchassée. 

public class Client implements Comparable<Client>{
   private String id ;
   //autres champs
   private BigDecimal totalDesAvoirs ;

   public int compareTo(Client autre) {
      return this.id.compareTo(autre.id) ;
   }

   // static "nested"  class
   // a accès aux données marquées +private+

   public static class ComparateurParRichesse implements Comparator<Client> {
      public int compare(Client c1, Client c2 ){
         return c1.totalDesAvoirs.compareTo(c2.totalDesAvoirs) ;
      }
   }
}

   Arrays.sort(tableauDesClients, new Client.ComparateurParRichesse() ) ;

Types membres d’instance ( inner types)

// code complexe !

public class  Entrepot implements Store<Produit> {
   private Produit[] stockage ;

   // "member inner class"
   //  Ici la classe est privée !
   //---------------------------------------------------------
   private class ProduitIterator implements Iterator<Produit> {

      // variables membres de la classe interne
      int index = 0 ;
      Produit produitSuivant = suivant() ;

      private Produit suivant() {
         Produit res = null ;
         while(index < stockage.length) {
            Produit courant = stockage[index++];
            if(courant != null) return courant ;
         }
         return res ;
      }

      // méthodes de la classe interne
      public boolean hasNext() { return  produitSuivant != null ; }
      public Produit next() {
         Produit res = produitSuivant ;
         produitSuivant = suivant() ; // renvoie null si fini de lire
         return res ;
      }

      public void remove() { throw new UnsupportedOperationException() ; }
   }
   //--------------------------------------------------------

   public Iterator<Produit> iterator() {
      return new ProduitIterator() ;
   }

   public Produit get(int ix) {
      // un code ....
   }
}

Ici les instances de la classe interne doivent être créées depuis une instance de la classe englobante (la classe interne est membre d’instance).

Le choix d’une telle classe peut être dicté par l’utilisation de membres de la classe englobante (l’instance de la classe interne conserve une référence sur l’instance englobante).

[Note]Point important

On remarquera que le code "client" du code précédent va utiliser les services d’une classe private lorsqu’il fera appel à la méthode iterator()

On a ici une illustration parfaite d’un point déjà soulevé sur l’ambiguïté du terme "accessibilité": on peut parfaitement utiliser un objet sans avoir accès à son type effectif! (Le même phénomène a été observé à propos du polymorphisme et de la portée entre modules)

On notera que l’on peut aussi créer une classe locale au code d’une méthode ou d’un constructeur. Dans ce cas les variables locales qui sont utilisées par la classe doivent être final .

[Avertissement]Attention

Toute classe interne liée à une instance garde en fait une référence sur sa classe englobante. (En fait les constructeurs sont implicitement implantés avec un premier argument qui est une référence du type de l’instance englobante!)

Attention donc aux problèmes liés au glaneur de mémoire.

Exercice

Ici l’exercice consiste à lire du code: dans le module java.base package java.util lire le code source de la classe LinkedList (comporte des classes membre d’instance - les Itérateurs - et une classe membre de classe Node !)

Classes anonymes

Il se trouve que parfois on n’a qu’une instance d’une classe interne. Dans ce cas il est possible de mettre dans un seul code la déclaration du code de classe et la création de l’instance: on a alors une classe anonyme.

Dans l’exemple suivant un TreeSet est une structure de donnée ordonnée. Un de ses constructeurs prend en paramètre un Comparator (qui lui permettra d’ordonner ses éléments).

Une classe anonyme. 

   TreeSet<Client> clientsParOrdreRichesse = new TreeSet<>( new Comparator<Client>() {
      public int compare(Client c1, Client c2 ){
         return c1.getRichesse().compareTo(c2.getRichesse()) ;
      }
   }) ;

Si ce code est dans une classe de nom MaClasse la compilation générera un fichier binaire du genre de MaClasse$1.class.

L’appel d’une classe anonyme peut se faire en rajoutant du code derrière:

  • un appel du constructeur sans paramètre d’une classe non final
  • un appel d’un constructeur avec paramètres d’une classe non final
  • un appel simulant l’existence d’un constructeur sans paramètre d’une interface (facilité syntaxique: il n’y a bien sûr pas de constructeur dans une interface). voir l’exemple ci-dessus.