Lorsqu'une classe implémente une interface :
class C implements I {
ou qu'une interface en étend une autre :
interface I2 extends I {
il s'agit d'héritage de type : les types C et I2 héritent
du type I, c'est à dire que partout où l'on utilise des objets
du type I, on pourra utiliser un objet de type I2 ou C.
Lorsqu'une classe en étend une autre :
class C2 extends C {
il y a à la fois héritage de type et héritage
d'implémentation. Malheureusement en Java, il est impossible de
faire de l'héritage d'implémentation seul (parfois appelé
héritage
privé). Si l'on veut implémenter une classe C2
à l'aide du code d'une classe C, sans qu'un objet instance de C2
ne soit également une instance de C, il faut utiliser le principe
de la délégation :
class C2 {
private C delegué;
Il est rare que l'on ait besoin d'utiliser à la fois les deux
héritages, car la liaison entre les classes est alors très
forte. Par conséquent, extends doit être manié
avec précaution. Dans les deux cas suivant, son utilisation est
incontournable :
class C2 extends C1 {
String vi = "C2.vi";
static String vc = "C2.vc";
void mi() {
System.out.println("Je
suis la méthode d'instance C2.mi()");
}
static void mc() {
System.out.println("Je
suis la méthode de classe C2.mc()");
}
}
class Test {
static void test(C1 o) {
System.out.println("Variable d'instance : "
+ o.vi);
System.out.println("Variable de classe : " +
o.vc);
o.mi();
o.mc();
}
public static void main(String [] args) {
C2 o = new C2();
test(o);
}
}
o est une instance de la classe C2, sous-classe de
la classe C1. C1 et C2 possèdent toutes
les deux une variable d'instance String vi, une méthode
d'instance void mi() , une variable de classe static
String vc, et une méthode de classe static void mc().
Dans la méthode Test.test(C1), le type déclaré
de o est C1, son type réel C2.
Alors, java Test provoquera l'affichage :
Variable d'instance : C1.vi
Variable de classe : C1.vc
Je suis la méthode d'instance C2.mi()
Je suis la méthode de classe C1.mc()
ce qui se traduit de la façon suivante : pour les méthodes
d'instance, la résolution de la méthode à exécuter
est dynamique et dépendra du type réel de l'objet et non
de son type déclaré. Dans tous les autres cas (méthode
de classe, variable de classe ou d'instance), seul le type déclaré
compte. Tout le travail de sélection est donc effectué à
la compilation.
Attention :
soit le code suivant :
class C1 {
void mi(C1 o) {
System.out.println("Je
suis la méthode d'instance C1.mi(C1)");
}
}
class C2 extends C1 {
void mi(C2 o) {
System.out.println("Je
suis la méthode d'instance C2.mi(C2)");
}
}
class Test {
public static void main(String [] args) {
C1 o = new C2();
o.mi(o);
}
}
java Test provoquera l'affichage :
Je suis la méthode d'instance C1.mi(C1)
En effet, la méthode à sélectionner est mi(C1)
! Or C2 ne redéfinit pas cette
méthode. Il se contente de la surcharger
par une méthode mi(C2). Par conséquent, la seule
méthode mi(C1) qu'une instance de C2 possède
est celle héritée de C1.
C'est pour cette raison que lorsque l'on redéfinit au niveau
d'une classe C la méthode equals(Object) héritée
de Object, il conviend d'écrire
class C {
public boolean equals(Object o) {
return o != null &&
o instanceof C && <test spécifique>;
}
}
C1() {
val = 0;
}
C1(int i) {
val = i;
}
int val() {
return val;
}
}
class C2 extends C1 {
int val;
C2() {
// appelle implicitement C1()
val = 1;
}
C2(int i) {
super(i-1); // appelle
explicitement C1(int)
val = i;
}
int val() {
return val;
}
void affiche() {
System.out.println("val
= " + val + ", super.val = " + super.val);
System.out.println("val()
= " + val() + ", super.val() = " + super.val());
}
}
class Test {
public static void main(String [] args) {
C2 o = new C2(3);
o.affiche();
}
}
java Test affichera :
val = 3, super.val = 2
val() = 3, super.val() = 2