• Aucun résultat trouvé

D - Les interfaces

Dans le document Télécharger cours Java en pdf (Page 56-63)

II - Classes et interfaces II-A - L' objet par l'exemple

II- D - Les interfaces

Une interface est un ensemble de prototypes de méthodes ou de propriétés qui forme un contrat. Une classe qui décide d'implémenter une interface s'engage à fournir une implémentation de toutes les méthodes définies dans l'interface. C'est le compilateur qui vérifie cette implémentation.

Voici par exemple la définition de l'interface java.util.Enumeration : Method Summary

boolean hasMoreElements() Tests if this enumeration contains more elements.

Object nextElement() Returns the next element of this enumeration if this enumeration object has

at least one more element to provide. Toute classe implémentant cette interface sera déclarée comme

public class C : Enumeration{ ...

boolean hasMoreElements(){....} Object nextElement(){...} }

Les méthodes hasMoreElements() et nextElement() devront être définies dans la classe C.

Considérons le code suivant définissant une classe élève définissant le nom d'un élève et sa note dans une matière : // une classe élève

public class élève{

// des attributs publics public String nom; public double note; // constructeur

public élève(String NOM, double NOTE){ nom=NOM;

note=NOTE; }//constructeur }//élève

Nous définissons une classe notes rassemblant les notes de tous les élèves dans une matière : // classes importées

// import élève // classe notes

public class notes{

// attributs

protected String matière; protected élève[] élèves;

// constructeur

public notes (String MATIERE, élève[] ELEVES){ // mémorisation élèves & matière

matière=MATIERE; élèves=ELEVES; }//notes

public String toString(){

String valeur="matière="+matière +", notes=("; int i;

// on concatène toutes les notes for (i=0;i<élèves.length-1;i++){

valeur+="["+élèves[i].nom+","+élèves[i].note+"],"; };

//dernière note

if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";} valeur+=")";

// fin

return valeur; }//toString

}//classe

Les attributs matière et élèves sont déclarés protected pour être accessibles d'une classe dérivée. Nous décidons de dériver la classe notes dans une classe notesStats qui aurait deux attributs supplémentaires, la moyenne et l'écart-type des notes :

public class notesStats extends notes implements Istats { // attributs

private double _moyenne; private double _écartType;

La classe notesStats dérive de la classe notes et implémente l'interface Istats suivante : // une interface

public interface Istats{ double moyenne(); double écartType(); }//

Cela signifie que la classe notesStats doit avoir deux méthodes appelées moyenne et écartType avec la signature indiquée dans l'interface Istats. La classe notesStats est la suivante :

// classes importées // import notes; // import Istats; // import élève;

public class notesStats extends notes implements Istats { // attributs

private double _moyenne; private double _écartType;

// constructeur

public notesStats (String MATIERE, élève[] ELEVES){ // construction de la classe parente

super(MATIERE,ELEVES); // calcul moyenne des notes double somme=0;

for (int i=0;i<élèves.length;i++){ somme+=élèves[i].note; } if(élèves.length!=0) _moyenne=somme/élèves.length; else _moyenne=-1; // écart-type double carrés=0;

for (int i=0;i<élèves.length;i++){

carrés+=Math.pow((élèves[i].note-_moyenne),2); }//for if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length); else _écartType=-1; }//constructeur // ToString

public String toString(){

}//ToString

// méthodes de l'interface Istats public double moyenne(){

// rend la moyenne des notes return _moyenne;

}//moyenne

public double écartType(){ // rend l'écart-type return _écartType; }//écartType

}//classe

La moyenne _moyenne et l'écart-type _ecartType sont calculés dès la construction de l'objet. Aussi les méthodes

moyenne et écartType n'ont-elles qu'à rendre la valeur des attributs _moyenne et _ecartType. Les deux méthodes

rendent -1 si le tableau des élèves est vide. La classe de test suivante :

// classes importées // import élève; // import Istats; // import notes; // import notesStats; // classe de test

public class test{

public static void main(String[] args){ // qqs élèves & notes

élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new

élève("jacques",18)};

// qu'on enregistre dans un objet notes notes anglais=new notes("anglais",ELEVES); // et qu'on affiche

System.out.println(""+anglais); // idem avec moyenne et écart-type anglais=new notesStats("anglais",ELEVES); System.out.println(""+anglais);

}//main }//classe

donne les résultats :

matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0])

matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0]),moyenne=16.0,écarttype= 1.632993161855452

Les différentes classes de cet exemple font toutes l'objet d'un fichier source différent : E:\data\serge\JAVA\interfaces\notes>dir 06/06/2002 14:06 707 notes.java 06/06/2002 14:06 878 notes.class 06/06/2002 14:07 1 160 notesStats.java 06/06/2002 14:02 101 Istats.java 06/06/2002 14:02 138 Istats.class 06/06/2002 14:05 247 élève.java 06/06/2002 14:05 309 élève.class 06/06/2002 14:07 1 103 notesStats.class 06/06/2002 14:10 597 test.java 06/06/2002 14:10 931 test.class

La classe notesStats aurait très bien pu implémenter les méthodes moyenne et écartType pour elle-même sans indiquer qu'elle implémentait l'interface Istats. Quel est donc l'intérêt des interfaces ? C'est le suivant : une fonction

peut admettre pour paramètre une donnée ayant le type d'une interface I. Tout objet d'une classe C implémentant l'interface I pourra alors être paramètre de cette fonction. Considérons l'interface suivante :

// une interface Iexemple

public interface Iexemple{ int ajouter(int i,int j); int soustraire(int i,int j); }//interface

L'interface Iexemple définit deux méthodes ajouter et soustraire. Les classes classe1 et classe2 suivantes implémentent cette interface.

// classes importées // import Iexemple;

public class classe1 implements Iexemple{ public int ajouter(int a, int b){ return a+b+10;

}

public int soustraire(int a, int b){ return a-b+20;

}

}//classe

// classes importées // import Iexemple;

public class classe2 implements Iexemple{ public int ajouter(int a, int b){ return a+b+100;

}

public int soustraire(int a, int b){ return a-b+200;

} }//classe

Par souci de simplification de l'exemple les classes ne font rien d'autre que d'implémenter l'interface Iexemple. Maintenant considérons l'exemple suivant :

// classes importées // import classe1; // import classe2; // classe de test

public class test{

// une fonction statique

private static void calculer(int i, int j, Iexemple inter){ System.out.println(inter.ajouter(i,j));

System.out.println(inter.soustraire(i,j)); }//calculer

// la fonction main

public static void main(String[] arg){

// création de deux objets classe1 et classe2 classe1 c1=new classe1();

classe2 c2=new classe2();

// appels de la fonction statique calculer calculer(4,3,c1);

calculer(14,13,c2); }//main

}//classe test

La fonction statique calculer admet pour paramètre un élément de type Iexemple. Elle pourra donc recevoir pour ce paramètre aussi bien un objet de type classe1 que de type classe2. C'est ce qui est fait dans la fonction main avec les résultats suivants :

21 127 201

On voit donc qu'on a là une propriété proche du polymorphisme vu pour les classes. Si donc un ensemble de classes Ci non liées entre-elles par héritage (donc on ne peut utiliser le polymorphisme de l'héritage) présentent un ensemble de méthodes de même signature, il peut être intéressant de regrouper ces méthodes dans une interface I dont hériteraient toutes les classes concernées. Des instances de ces classes Ci peuvent alors être utilisées comme paramètres de fonctions admettant un paramètre de type I, c.a.d. des fonctions n'utilisant que les méthodes des objets Ci définies dans l'interface I et non les attributs et méthodes particuliers des différentes classes Ci.

Dans l'exemple précédent, chaque classe ou interface faisait l'objet d'un fichier source séparé : E:\data\serge\JAVA\interfaces\opérations>dir 06/06/2002 14:33 128 Iexemple.java 06/06/2002 14:34 218 classe1.java 06/06/2002 14:32 220 classe2.java 06/06/2002 14:33 144 Iexemple.class 06/06/2002 14:34 325 classe1.class 06/06/2002 14:34 326 classe2.class 06/06/2002 14:36 583 test.java 06/06/2002 14:36 628 test.class

Notons enfin que l'héritage d'interfaces peut être multiple, c.a.d. qu'on peut écrire

public class classeDérivée extends classeDeBase implements i1,i2,..,in{ ...

}

où les ij sont des interfaces.

II-E - Classes anonymes

Dans l'exemple précédent, les classes classe1 et classe2 auraient pu ne pas être définies explicitement. Considérons le programme suivant qui fait sensiblement la même chose que le précédent mais sans la définition explicite des classes classe1 et classe2 :

// classes importées // import Iexemple; // classe de test

public class test2{

// une classe interne

private static class classe3 implements Iexemple{ public int ajouter(int a, int b){

return a+b+1000; }

public int soustraire(int a, int b){ return a-b+2000;

}

};//définition classe3

// une fonction statique

private static void calculer(int i, int j, Iexemple inter){ System.out.println(inter.ajouter(i,j));

System.out.println(inter.soustraire(i,j)); }//calculer

// la fonction main

public static void main(String[] arg){

// création de deux objets implémentant l'interface Iexemple Iexemple i1=new Iexemple(){

return a+b+10; }

public int soustraire(int a, int b){ return a-b+20;

}

};//définition i1

Iexemple i2=new Iexemple(){

public int ajouter(int a, int b){ return a+b+100;

}

public int soustraire(int a, int b){ return a-b+200;

}

};//définition i2 // un autre objet Iexemple Iexemple i3=new classe3();

// appels de la fonction statique calculer calculer(4,3,i1);

calculer(14,13,i2); calculer(24,23,i3); }//main

}//classe test

La particularité se trouve dans le code :

// création de deux objets implémentant l'interface Iexemple Iexemple i1=new Iexemple(){

public int ajouter(int a, int b){ return a+b+10;

}

public int soustraire(int a, int b){ return a-b+20;

}

};//définition i1

On crée un objet i1 dont le seul rôle est d'implémenter l'interface Iexemple. Cet objet est de type Iexemple. On peut donc créer des objets de type interface. De très nombreuses méthodes de classes Java rendent des objets de type interface c.a.d. des objets dont le seul rôle est d'implémenter les méthodes d'une interface. Pour créer l'objet i1, on pourrait être tenté d'écrire :

Iexemple i1=new Iexemple()

Seulement une interface ne peut être instantiée. Seule une classe implémentant cette interface peut l'être. Ici, on définit une telle classe "à la volée" dans le corps même de la définition de l'objet i1 :

Iexemple i1=new Iexemple(){

public int ajouter(int a, int b){ // définition de ajouter }

public int soustraire(int a, int b){ // définition de soustraire }

};//définition i1

La signification d'une telle instruction est analogue à la séquence :

public class test2{ ... // une classe interne

private static class classe1 implements Iexemple{ public int ajouter(int a, int b){

// définition de ajouter }

// définition de soustraire }

};//définition classe1 ...

public static void main(String[] arg){ ...

Iexemple i1=new classe1(); }//main

}//classe

Dans l'exemple ci-dessus, on instantie bien une classe et non pas une interface. Une classe définie "à la volée" est dite une classe anonyme. C'est une méthode souvent utilisée pour instantier des objets dont le seul rôle est d'implémenter une interface.

L'exécution du programme précédent donne les résultats suivants : 17 21 127 201 1047 2001

L'exemple précéent utilisait des classes anonymes pour implémenter une interface. Celles-ci peuvent être utilisées également pour dériver des classes n'ayant pas de constructeurs avec paramètres. Considérons l'exemple suivant :

// classes importées // import Iexemple;

class classe3 implements Iexemple{ public int ajouter(int a, int b){ return a+b+1000;

}

public int soustraire(int a, int b){ return a-b+2000;

}

};//définition classe3

public class test4{

// une fonction statique

private static void calculer(int i, int j, Iexemple inter){ System.out.println(inter.ajouter(i,j));

System.out.println(inter.soustraire(i,j)); }//calculer

// méthode main

public static void main(String args[]){

// définition d'une classe anonymé dérivant classe3 // pour redéfinir soustraire

classe3 i1=new classe3(){

public int ajouter(int a, int b){ return a+b+10000;

}//soustraire };//i1

// appels de la fonction statique calculer calculer(4,3,i1);

}//main }//classe

Nous y retrouvons une classe classe3 implémentant l'interface Iexemple. Dans la fonction main, nous définissons une variable i1 ayant pour type, une classe dérivée de classe3. Cette classe dérivée est définie "à la volée" dans une classe anonyme et redéfinit la méthode ajouter de la classe classe3. La syntaxe est identique à celle de la classe anonyme implémentant une interface. Seulement ici, le compilateur détecte que classe3 n'est pas une interface mais une classe. Pour lui, il s'agit alors d'une dérivation de classe. Toutes les méthodes qu'il trouvera dans le corps de la classe anonyme remplaceront les méthodes de même nom de la classe de base.

L'exécution du programme précédent donne les résultats suivants : E:\data\serge\JAVA\classes\anonyme>java test4

10007 2001

Dans le document Télécharger cours Java en pdf (Page 56-63)