next up previous contents index
suivant: 5.4 Erreurs et exceptions monter: 5. Tout un monde précédent: 5.2 Tableaux d'objets   Table des matières   Index


5.3 Interfaces

Une des exigences de l'ingénierie du logiciel est la production de composants assemblables et utilisables dans plusieurs applications, comme dans toute autre activité industrielle. La mise en \oeuvre de cette exigence dans un langage de programmation est permise, d'une part par la notion de module, d'autre part par des règles d'écriture et d'usage de ces modules. Plusieurs langages, notamment Modula 2 et Ada, ont été conçus autour de la notion de module. L'idée est qu'un programme est formé à partir de plusieurs modules ; les entités définies dans chaque module sont classées en deux catégories : publiques ou privées. Les entités publiques d'un module sont déclarées dans son interface et sont exportables vers les autres modules, lesquels déclarent dans leurs interfaces les entités qu'ils importent. Les modules exportant des entités offrent des services dont sont clients les modules qui les importent.

En Java, les interfaces sont des types qui permettent de représenter l'interface d'un module. Les interfaces portent souvent des noms se terminant en able.

5.3.0.0.1 Exemple : ordre naturel dans une classe

L'interface Comparable, qui est membre du paquet java.lang, déclare une méthode de comparaison des objets :

package java.lang;

public interface Comparable {
  int compareTo(Object o);
}

La documentation indique que la méthode compareTo, appliquée à un objet $x$, avec pour argument un objet $y$, compare les objets $x$ et $y$ et retourne un entier $<0$, ou $0$ ou un entier $>0$, selon que $x$ est plus petit que $y$, lui est égal ou lui est supérieur ; elle déclenche l'exception ClassCastException si le type de $y$ ne permet pas la comparaison avec $x$. La documentation ne dit pas comment cette comparaison s'effectue, ce qui n'aurait évidemment aucun sens pour des objets quelconques : une telle méthode de comparaison n'est d'ailleurs définie que dans des classes qui réalisent l'interface Comparable. On dit alors qu'une telle classe est munie d'un ordre naturel. Ainsi, les classes Character, Double, String, Integer réalisent cette interface, mais la classe Object ne la réalise pas.

Par exemple, la classe Integer pourrait réaliser l'interface Comparable grâce à la définition suivante de compareTo :

package java.lang;

public class Integer {
  // ...
  public int compareTo(Object o) {
    int n = ((Integer)o).intValue();
    int m = this.intValue();
    return (m<n ? -1 : (m==n ? 0 : 1));
  }
}

Toute interface est un sous-type d'Object. Une classe qui réalise une interface est un sous-type de cette interface. On peut donc affecter à une variable de type l'interface une expression d'une classe la réalisant, par exemple :

    Comparable c = new Integer(3);

On peut aussi passer à une méthode un argument de type Integer si le paramètre correspondant est déclaré de type Comparable. Par exemple, si nous voulons définir une fonction min qui calcule le minimum de deux objets, nous devons supposer que son premier argument est comparable à son second argument : il suffit de déclarer le premier argument de type Comparable :

  static Object min(Comparable x, Object y) {
    return x.compareTo(y) <=0 ? x : y;
  }

On pourra alors invoquer cette fonction, par exemple, sur des instances d'Integer5.1 :

    Object m = min(new Integer(3), new Integer(2));

ou, si l'on veut obtenir un Integer, à l'aide d'un transtypage :

    Integer m = (Integer) min(new Integer(3), new Integer(2));


5.3.0.0.2 Paramètres fonctionnels

Java ne permet pas, à la différence de certains langages (C, C++, et bien sûr les langages fonctionnels comme Ocaml) à une fonction d'avoir un paramètre fonctionnel. Ceci s'avère pourtant utile : passer en argument une fonction de comparaison à une fonction de tri, ou une fonction de traitement des éléments à une fonction de parcours d'une structure de données. L'usage des interfaces permet d'y suppléer.

Ainsi, il arrive qu'une classe ne soit pas naturellement ordonnée, mais que diverses relations d'ordre puissent être utilisées. Dans ce cas, on recourt à l'interface Comparator :

package java.lang;

public interface Comparator {
  public int compare(Object o1, Object o2);
  public boolean equals(Object obj);  // définie dans Object
}

On peut alors définir une autre fonction min qui, au lieu de supposer que ses deux arguments appartiennent à un type naturellement ordonné, accepte un troisième argument de type Comparator :

  static Object min(Object x, Object y, Comparator c) {
    return c.compare(x,y) <=0 ? x : y;
  }

Par exemple, considérons la classe Personne, composée des champs nom et prénom de type String (on pourrait y ajouter la date de naissance, le domicile, etc.) :

class Personne {
  private String prénom, nom;

  String getNom() {return nom;}
  String getPrénom() {return prénom;}

  public Personne(String prénom, String nom) {
    if (prénom==null || nom==null)
      throw new NullPointerException();
    this.prénom = prénom;
    this.nom = nom;
  }
 // ...
}

Il est facile de lui donner un ordre naturel en lui ajoutant une méthode compareTo, par exemple pour l'ordre lexicographique sur les noms :

class Personne implements Comparable {
  // ...  
  public int compareTo(Object o) {
    Personne n = (Personne)o;
    int compNom = nom.compareTo(n.nom);
    return 
      compNom!=0 ? compNom : prénom.compareTo(n.prénom);
  }
  
  public boolean equals(Object o) {
    return
      o instanceof Personne &&
      ((Personne)o).prénom.equals(prénom) &&
      ((Personne)o).nom.equals(nom);
  }

  public int hashCode() {
    return 31*prénom.hashCode() + nom.hashCode();
  }
}

On notera que les méthodes equals et compareTo se comportent différemment si l'objet n'a pas le type requis, ici Personne : le test o instanceof Personne permet à equals(Object) de retourner false, tandis que compareTo(Object), ne procédant pas à ce test, peut déclencher l'exception ClassCastException due au transtypage (Personne)o.

D'autre part, toute classe qui redéfinit equals doit aussi redéfinir hashCode ; en effet, deux objets égaux par equals doivent avoir la même valeur de hachage par hashCode. Ces contraintes (sur equals, compareTo, hashCode, etc.) doivent être respectées, afin d'assurer à l'utilisateur que les méthodes qui les utilisent (par exemple, Collections.sort, etc.) font bien ce qu'elles sont censées faire.

Il arrive que des instances de Personne doivent être comparées selon d'autres relations d'ordre : parfois, selon le nom, parfois selon le prénom, etc. On est alors conduit à réaliser l'interface Comparator sera par exemple, pour comparer selon le prénom (et pour deux prénoms égaux, selon le nom) :

class PrénomComparator implements Comparator {
   public int compare(Object o1, Object o2) {
     Personne r1 = (Personne) o1;
     Personne r2 = (Personne) o2;
     int prénomComp = 
       r1.getPrénom().compareTo(r2.getPrénom());
     if (prénomComp != 0) 
       return prénomComp;
     else
       return r1.getNom().compareTo(r2.getNom());
   }
}
On peut alors créer un objet comparateur, et le passer en argument à certaines méthodes qui l'utilisent, par exemple :

class TestComparaison {
  public static void main(String[] args) {
    Comparator prénomComparator = new PrénomComparator();
    Nom
      turing = new Personne("Alan", "Turing"),
      neumann = new Personne("John", "von Neumann");
    Personne m = 
      (Personne) min(turing, neumann, new Prénomcomparator());
  }
}


5.3.0.0.3 Réalisations anonymes d'une interface

L'utilisation de l'interface Comparator serait lourde si l'on devait définir une classe de réalisation pour chaque méthode de comparaison ; ceci peut être évité, car Java permet d'instancier des classes anonymes :

class TestComparaison {
  // ...
  public static void main(String[] args) {
    Comparator prénomComparator = new Comparator() {
      public int compare(Object o1, Object o2) {
        Personne r1 = (Personne) o1;
        Personne r2 = (Personne) o2;
        int prénomComp = r1.getPrénom().compareTo(r2.getPrénom());
        if (prénomComp != 0) 
          return prénomComp;
        else
          return r1.getNom().compareTo(r2.getNom());
      }
    };
    // ...
    Personne m = (Personne) min(turing, neumann, prénomComparator);
  }
}

L'expression new Comparator() { ... }, qui utilise le nom de l'interface, permet à la fois de créer une instance et de définir la méthode compare().


next up previous contents index
suivant: 5.4 Erreurs et exceptions monter: 5. Tout un monde précédent: 5.2 Tableaux d'objets   Table des matières   Index
Rene' LALEMENT 2001-11-07