Le langage Java ne permet pas de l'héritage multiple . Il pallie ce manque par l'introduction des interfaces . Le choix délibéré de Java de supprimer l'héritage multiple est dicté par un souci de simplicité.
A TERMINER
héritage diamant
Pour supprimer les problèmes liés à l'héritage multiple, les interfaces sont des ``sortes de classes'' qui ne possèdent que des champs static final (autant dire des constantes) et des méthodes abstraites.
En fait, les interfaces sont un moyen de préciser les services qu'une classe peut rendre. On dira qu'une classe implante une interface Z si cette classe fournit les implantations des méthodes déclarées dans l'interface Z. Autrement dit, la définition d'une interface consiste se donner une collection de méthodes abstraites et de constantes. L'implantation de ces méthodes devra évidemment être fournie par les classes qui se réclament de cette interface.
Bref, une interface est une classe abstraite dans laquelle, il n'y a ni variables d'instances et ni méthodes non abstraites.
Les interfaces ne fournit pas l'héritage multiple car :
interface Service { int MAX = 1024 ; ... int une_méthode(...) ; ... }
Par convention les noms des interfaces commencent par une lettre majuscule et se terminent par ``able'' ou ``ible''. Dans nos exemples, on n'appliquera pas ces conventions.
Contrairement aux classes, on ne peut qualifier une interface de private ou protected.
Les champs d'une interface sont des champs static. Toutes les règles d'initialisation des champs statiques s'appliquent ici.
Les qualifiers transcient, volatile ou synchronized ne peuvent être utilisés pour les membres des interfaces. De même, les qualifiers private et protected ne doivent être utilisés pour les membres des interfaces.
Les interfaces définissent des ``promesses de services''. Mais seule une classe peut rendre effectivement les services qu'une interface promet. Autrement dit, l'interface toute seule ne sert à rien : il nous faut une classe qui implante l'interface. Une classe qui implante une interface le déclare dans son entête
class X implements Service { ... int une_méthode(...) { ... } ... }
Par cette déclaration, la classe X promet d'implanter toutes les méthodes déclarées dans l'interface Service. La classe X doit donc fournir l'implantation des méthodes précisées dans l'interface Service; on devra donc trouver dans le définition de cette classe, l'implantation de la la méthode une_méthode.
La signature de la méthode doit évidemment être la même que celle promise par l'interface. Dans le cas contraire, la méthode est considérée comme une méthode de la classe et non une implantation de l'interface.
Si une méthode de même signature existe dans la classe mais avec un type de retour différent une erreur de compilation est générée.
Service s ;
Qu'est-ce qu'une référence de type interface ? Et bien, c'est une référence contient
Pourquoi vouloir désigner un objet, non pas par rapport à sa classe, mais par rapport à l'interface qu'elle cette implante ? Voici un exemple d'utilisation. Soit l'interface suivante :
interface Représentable { void dessiner(); void tourner(int angle); void translater(int depl); }
permettant de dessiner et effectuer une rotation ou une translation sur des formes géométriques. Chaque forme (un carrée, un cercle, un polygone, etc.) devra implanter cette interface pour faire partie de l'ensemble de formes représentatable.
Imaginons, à présent, une classe Dessin veuille gérer un tableau de formes représentables. Quel est le type de ce tableau ?
La première solution consiste à définir un tableau Object [] en précisant que les éléments sont des instances de la classe Object; ce qui sera toujours vrai puisque la classe Object est la classe ancêtre de toute les classes. Cette solution est évidemment à éviter car ce tableau pourra, certes, contenir des formes représentables mais aussi n'importe quelle autre instance. Cette définition est bien trop large !
Comment faire pour restreindre le type des objets, que ce tableau peut contenir, aux seuls instances des classes ayant implanté l'interface Représentables ? Pour cela, il suffit de définir un tableau de type interface :
class Dessin { private Représentable[] listeFormes; ... }
Ainsi, cette classe pour, par exemple, implanter une méthode dessinerTout qui dessine l'ensemble des formes:
class Dessin { private Représentable[] listeFormes; ... public void dessinerTout() { for (i = 0; i < sommet; i++) listeFormes[i].dessiner() } }
interface A extends X { ... } interface A extends X, Y, Z { ... }
Une interface dérivée hérite de toutes les constantes et méthodes des interfaces ancêtres; à moins qu'un autre champ de même nom ou une autre méthode de même signature soit redéfinie dans l'interface dérivée.
Tout comme pour les classes, les champs des interface peut être redéfinis dans une interface dérivée.
interface A { int info = 1; } interface B extends A { int info = 2; }
La définition du champ info dans l'interface B masque la définition du champ info de l'interface A. Pour parler du champ info de l'interface A, on le notera A.info.
Un même champ peut être hérité de plusieurs manière pour une même interface:
Le champ infoA est hérité par l'interface D de deux manières: une fois par l'interface B et une autre fois par celle de C. Mais il n'existera pas deux champs infoA dans l'interface D; il n'y en aura qu'un.
interface A { char infoA = 'A'; } interface B extends A { char infoB = 'B'; } interface C extends A { char infoB = 'C'; } interface D extends B, C { char infoD = 'D'; }