9. L’héritage
Définir une nouvelle classe, dite classe dérivée,
à partir d’une classe existante dite classe de base.
Remarques:
- Spécialisation.
- "is a" relation.
9.1
Définition d'une classe dérivée
public class Point {
protected double x=3.4, y=5.6 ;
public String toString(){
return "point: (" + x + "," +
y+")" ;
}
public void move(double dx, double dy){
System.out.println("Moving the
point");
x+=dx;
y+=dy;
}
}
-------------------------------------------------
public class PointC extends Point{
private byte col=3;
public void dspl(){
System.out.println("point
col:("+x+"," +y+","+col+")");
}
public void inc(){
x++; y++;
}
}
--------------------------------------------
public class Test {
public static void main(String arg[]){
Point p = new Point();
System.out.println(""+p);
PointC pc = new PointC();
pc.inc();
pc.dspl();
}
}
Remarques:
- Avant de compiler une classe dérivée, la machine
virtuelle Java doit compiler sa classe de base.
- Une méthode d’une classe dérivée
n’a pas accès aux membres privés de sa classe de
base.
- Une méthode d’une classe
dérivée a accès aux
membres protégés
de sa classe de base.
- Si elle se trouve dans le même package, une méthode
de classe dérivée a accès aux membres sans
modificateur de visibilité de sa classe de base.
- Les membres publics, hérités de la classe de base,
se comportent comme des membres publics de la classe
dérivée.
- Les membres protégés, hérités de la
classe de base, se comportent comme des membres protégés
de la classe dérivée.
- Les membres privés de la classe de base se comportent
comme des membres privés depuis l’extérieur de la
classe dérivée.
9.2 Les
variables avec le même nom
Il est possible que dans la classe de
base et
dans la classe dérivée sont déclarées des
variables qui portent le même nom.
Dans ce cas les deux variables coexistent, mais dans la classe
dérivée la
variable locale masque (fait l'ombre à) la variable de la classe
de base. Si on
veut utiliser la variable de la classe de base il faut utiliser le mot
clé super.
public class Point {
protected double x=3.4, y=5.6 ;
protected String name ="base point";
public String toString(){
return "point: (" + x + "," +
y+")" ;
}
public void move(double dx, double dy){
System.out.println("Moving the
point");
x+=dx;
y+=dy;
}
}
-------------------------------------------------
public class PointC extends Point{
private byte col=3;
private String name ="color point";
public void dspl(){
System.out.print("point
col:("+x+","+y+
","+col+")");
System.out.print("\tname:"+name);
//name locale
System.out.println("\tname
of parent object:"+super.name);
}
public void inc(){
x++; y++;
}
}
-----------------------------------------------------------------------
public class
Test {
public
static void main(String arg[]){
Point
p = new Point();
System.out.println(""+p);
PointC pc = new PointC();
pc.inc();
pc.dspl();
}
}
9.3
Construction et initialisation des objets dérivés
9.3.1 Appels
des constructeurs
Dans l'exemple suivant la classe Point dispose de deux constructeurs
– sans arguments et avec deux arguments. Les deux constructeurs
affichent sur la console un message correspondant, juste pour savoir
quel constructeur est appelé.
public class Point {
protected double x, y;
public Point(){
System.out.println("Constructor base no prs");
x=3.4; y=5.6;
}
public Point(double x, double y){
System.out.println("Constructor base 2 prs "+x+","+y);
this.x=x;
this.y=y;
}
public String toString(){
return "point: (" + x + ","
+ y+")" ;
}
public void move(double dx, double dy){
System.out.println("Moving
the point");
x+=dx;
y+=dy;
}
}
----------------------------------------------------------------------
public class PointC extends Point{
private byte col;
public PointC(){
System.out.println("Constructor drvd no par");
col=3;
}
public PointC(double x,double y,byte col){
super(x,y);
System.out.println("Constructor drvd 3 par "+x+","+y+","+col);
this.col = col;
}
public void dspl(){
System.out.println("point
col:("+x+","+y+","+col+")");
}
public void inc(){
x++; y++;
}
}
--------------------------------------------------------------------------
public class Test {
public static void main(String arg[]){
Point p = new Point();
Point p1 = new
Point(1.4,2.6);
System.out.println(p
+" "+p1);
PointC pc = new PointC();
pc.dspl();
PointC pc1 = new PointC(4.1,
5.2,(byte)4);
pc1.dspl();
}
}
La construction de l'objet de la
classe dérivée pc est fait par le
constructeur sans
arguments. On voit des messages affichés, qu'avant
de faire quoi que soit le constructeur fait un appel de constructeur de
la
classe de base sans argument. Si dans la classe de base un tel
constructeur
n'existe pas il y aura une faute.
La construction de l'objet pc2 montre une autre possibilité. Dans se cas le
constructeur avec
deux arguments de la classe base est appelé. Les arguments
nécessaires son
livré par le mot clé super. L'appel explicite de constructeur de
la classe de base par le mot clé
super devrait être obligatoirement la première instruction
dans le constructeur
de la classe dérivé.
Remarques:
- En Java, le constructeur de la classe dérivée doit
prendre en charge l’intégralité de la construction
de l’objet. Pour initialiser les membres variables de la clase de
base il faut recourir à l'un de ces constructeurs
- L'appel de constructeur de la classe de base pourrait se faire
implicitement ou explicitement.
- Si un constructeur d’une classe dérivée
appelle un constructeur d’une classe de base implicitement, il
s'agit d'un constructeur sans arguments. L'absence d'un tel
constructeur génère une faute.
- Si un constructeur d’une classe dérivée
appelle un constructeur d’une classe de base explicitement, il
doit obligatoirement s’agir de la première instruction du
constructeur et ce dernier est désigné par le
mot-clé super.
- L’appel par super ne concerne que le constructeur de la
classe de base du niveau immédiatement supérieur
9.3.2
Initialisation d’un objet dérivé
class B extends A { ..... }
La création d’un objet de type B se déroule en 6
étapes.
• Allocation mémoire pour un objet de
type B. Il s’agit bien de l’intégralité de la
mémoire nécessaire pour un tel objet, et pas seulement
pour les variables non hérités de A.
• Initialisation par défaut de tous les
variables de B (aussi bien ceux hérités de A, que ceux
propres à B aux valeurs "nulles" habituelles).
• Initialisation explicite, s’il y a lieu,
des variables hérités de A
• Exécution du constructeur choisi de A.
• Initialisation explicite, s’il y a lieu,
des variables propres à B.
• Exécution du constructeur choisi de B
9.4
Dérivations successives
Une classe dérivée
peut, à son tour, servir de classe de base pour une autre.
D’une même classe peuvent être dérivées
plusieurs classes différentes.
Fig. 9.1 Arborescence des classes
dérivées
• En Java une
classe dérivée ne
peut avoir qu'une seule classe de base au niveau immédiatement
supérieur
9.5
Redéfinition et surcharge de méthodes
9.5.1 Surcharge
de méthodes (overloading)
La notion de surcharge (des méthodes qui porte le
même nom mais qui se different par leurs arguments) se
généralise dans le cadre de l’héritage: une
classe dérivée pourra à son tour surcharger une ou
plusieurs méthodes d’une classe ascendante. Bien entendu,
la nouvelle méthode ne deviendra utilisable que par la classe
dérivée ou ses descendantes, mais pas par ses ascendantes.
public class Point {
protected double
x, y;
public
Point(double x, double y){
System.out.println("Constructor base");
this.x=x; this.y=y;
}
public
String toString(){
return "point: (" + x + "," + y+")" ;
}
public void
move(double dx, double dy){
System.out.println("Moving the point");
x+=dx; y+=dy;
}
}
-----------------------------------------------------------
public class PointC extends Point{
private byte col;
public
PointC(double x,double y,byte col){
super(x,y);
System.out.println("Construct drvd");
this.col = col;
}
public void dspl(){
System.out.println("point col:("
+x+","+y+","+col+")");
}
public void
move(double dxy){
System.out.println("Moving overloaded ");
x+=dxy; y+=dxy;
}
}
-------------------------------------------------------------
public class Test {
public static void main(String arg[]){
Point p1 = new Point(1.4,2.6);
System.out.println(""+p1);
p1.move(2.2,3.5);
//p1.move(4.4);
error not in the base class
System.out.println(""+p1);
PointC pc = new
PointC(2.1,3.2,(byte)3);
pc.dspl();
pc.move(1.4, 2.3);
// inherited method
pc.dspl();
pc.move(3.3);
//overloaded method
pc.dspl();
}
}
--------------------------------------------------------------
Remarque:
- Le choix de la méthode se fait pendant la compilation de
la classe.
9.5.2
Redéfinition de méthodes (overriding)
La redéfinition (en anglais overriding) de méthodes
représente une technologie puissante en Java. Elle permet
à une classe dérivée de redéfinir une
méthode de sa classe de base, en proposant une nouvelle
définition. Il faut respecter la signature de la méthode
(type et nombre des arguments), ainsi que le type de la valeur de
retour.
public class Point {
protected double
x, y;
public
Point(double x, double y){
System.out.println("Constructor base");
this.x=x; this.y=y;
}
public
String toString(){
return "point: (" + x + "," + y+")" ;
}
public void
move(double dx, double dy){
System.out.println("Moving the point");
x+=dx; y+=dy;
}
}
-----------------------------------------------------------------
public class PointC extends Point{
private byte col;
public PointC(double x,double y,byte col){
super(x,y);
System.out.println("Construct
drvd");
this.col = col;
}
public String toString(){
//overrided
return "color "+col+"
"+super.toString();
}
public void move(double dxy){
System.out.println("Moving
overloaded ");
x+=dxy; y+=dxy;
}
}
----------------------------------------------------------------------------
public class Test {
public static void main(String arg[]){
PointC pc = new
PointC(1.3,2.4,(byte)2);
System.out.println("after
creating "+pc);
pc.move(2, 2);
System.out.println("after
first move "+pc);
pc.move(3);
System.out.println("after
second move "+pc);
}
}
----------------------------------------------------------------------------
La nouvelle méthode doit retourner une valeur d’un
type identique ou dérivé de celui de la méthode
qu’elle redéfinit.
La redéfinition d’une méthode ne doit pas diminuer
les droits d’accès à cette méthode. En
revanche, elle peut les augmenter.
class A {
public void f (int
n) { ..... }
}
class B extends A {
private void f
(int n) { //erreur!
.....
}
}
----------------------------------------------------------------
class A {
private void f
(int n) { ..... }
}
class B extends A {
public void f (int
n) { //OK!
.....
}
}
--------------------------
Une méthode statique ne peut pas être redéfinie
dans une classe dérivée.
Remarque:
- Le choix de la méthode se fait pendant l'exécution
du programme.
9.6 Le
polymorphisme
Un concept extrêmement puissant en P.O.O., qui complète
l’héritage. On peut caractériser le polymorphisme
en disant qu’il permet de manipuler des objets sans en
connaître exactement leur type.
Il faut noter qu'il ne s’agit pas de traiter
ainsi n’importe quel objet. Le polymorphisme exploite la
relation induite
par l’héritage en appliquant la règle suivante : un
objet d'une classe dérivée "is a" un objet de classe de
base. Un point coloré est aussi un
point, on peut donc bien le traiter comme un point.
9.6.1 Les bases
Considérons la situation dans laquelle les classes "Point" et
"PointC" disposent chacune d’une méthode "toString()",
ainsi que des constructeurs habituels:
public class Point {
public Point (int
x, int y) { ..... }
public String
toString() { ..... }
}
public class PointC extends Point {
public PointC (int
x, int y, byte couleur) { ..... }
public String
toString() { ..... }
}
------------------------
Point p ;
p = new Point (2.3, 5.4);
Fig. 9.2 Objet de type Point
p = new PointC (2.3, 5.4, (byte)7) ;
// p de type Point contient la référence à un
objet de type PointC
Fig. 9.3 Objet PointC
référencé par Point
D’une manière générale, Java permet
d’affecter à un variable objet non seulement la
référence à un objet du type correspondant, mais
aussi une référence à un objet d’un type
dérivé. On peut dire qu’on
est en présence d’une
conversion implicite (légale) d'un objet de type
dérivé vers un objet de classe de base (upcasting).
On parle aussi de compatibilité par affectation entre
un type classe et un type ascendant.
Point p ;
p = new Point (2.3, 5.4);
System.out.println(""+p);
// appelle la
méthode toString() de la classe Point
p = new PointC (2.3, 5.4, (byte)7) ;
System.out.println(""+p);
// appelle la
méthode toString() de la classe PointC
Ce choix d’une méthode au moment de
l’exécution (et non plus de la compilation) porte
généralement le nom de ligature dynamique (ou encore de
liaison dynamique).
Le polymorphisme permet d’obtenir un comportement adapté
à chaque type d’objet, sans avoir besoin de tester sa
nature.
-------------------------------------------
tableau "hétérogène" d’objets
public class Point {
protected double
x, y;
public
Point(double x, double y){
System.out.println("Constructor point:"
+x+","+y);
this.x=x; this.y=y;
}
public
String toString(){
return "point: (" + x + "," + y+")" ;
}
}
public class PointC extends Point{
private byte col;
public
PointC(double x,double y,byte col){
super(x,y);
System.out.println("Construct PointC col="+col);
this.col = col;
}
public String
toString(){
return "color "+col+" "+super.toString();
}
}
public class Test {
public static void
main(String arg[]){
Point p[] = new Point[6];
for(int i =0; i< p.length;i++){
if(Math.random()>0.5){
p[i]= new Point(5*i,2*i);
}
else{
p[i]= new PointC(3*i,4*i,(byte)i);
}
}
System.out.println("\nThe points in the array are");
for(int i =0;i<p.length;i++){
System.out.println(i+":"+p[i]);
}
}
}
9.6.2
Généralisation à plusieurs classes
Les classes marquées d’un astérisque
définissent ou redéfinissent une méthode fx().
Fig.9.4. Polymorphisme en arborescence des classes
dérivées
Supposons qu'on a créé des références
à partir de chaque classe de la Fig. 9.4:
A a ; B b ; C c ; D d ; E e ; F f ;
Les affectations suivantes sont légales:
a = b ; a = c ; a = d ; a = e ; a =f ;
b = d ; b = e ;
c = f ;
En revanche, celles-ci ne le sont pas :
b = a ;
// erreur : A ne descend pas de B
d = c ;
// erreur : C ne descend pas de D
c = d ;
// erreur : D ne descend pas de C
Appel de la méthode fx()
a=new A();
a.fx();
méthode fx() de A
a=new B();
a.fx();
méthode fx() de A
a=new C();
a.fx();
méthode fx() de C
a=new D();
a.fx();
méthode fx() de D
a=new E();
a.fx();
méthode fx() de A
a=new F();
a.fx();
méthode fx() de C
9.6.3 Les
conversions explicites de références
La conversion implicite d'un objet de type de
base
vers un objet de type dérivé (downcasting) n'est
pas permis!
Point p ;
PointC pc = new PointC(...), pc2 ;
.....
p = pc ;
// p contient
la référence à un objet de type PointC
.....
pc2 = p ;
// refusé en compilation
Par contre la conversion "downcasting"explicite et
acceptée en compilation, mais il faut s'assurer que la
référence de type de base est orientée à un
objet du type dérivé.
pc2 = (PointC) p ; // accepté en
compilation, ClassCastException possible
// lors
execution
operateur instanceof pour verifier On peut
s’assurer qu’un objet est bien une instance d’une
classe donnée en recourant à l’opérateur
instanceof. Par exemple, l’expression
"p instanceof PointC" vaudra true si p est (exactement) de type PointC
p instanceof PointC
//true /false
9.7
La superclasse Object
Il existe une classe nommée Object dont toutes les classes en Java sont implicitement
dérivées
class Point {
.....
}
tout se passe en fait comme si vous aviez écrit (vous pouvez
d’ailleurs le faire):
class Point extends Object {
.....
}
----------
Point p = new Point (...) ;
PointC pc = new PointC (...) ;
Object obj ;
obj = p ;
// OK
obj = pc ;
// OK
--------------
La classe Object dispose de quelques
méthodes
qu’on peut soit utiliser telles quelles, soit redéfinir.
Les plus importantes
sont toString() et equals().
toString() - fournit
le nom de la classe concernée et l'adress de l'objet en
hexadecimale (précédé de @)
package deriv1;
public class Point {
protected double
x, y;
public
Point(double x, double y){
System.out.println("Constructor point:"
+x+","+y);
this.x=x; this.y=y;
}
}
-----------------
package deriv1;
public class Test3 {
public static void
main(String arg[]){
Point p = new Point(3.2,4.3);
System.out.println(""+p);
}
}
------------------
equals() - se contente de
comparer les adresses des deux objets
public class Point {
protected double
x, y;
public
Point(double x, double y){
System.out.println("Constructor point:"
+x+","+y);
this.x=x; this.y=y;
}
}
------------------
public class Test3 {
public static void
main(String arg[]){
Point p = new Point(3.2,4.3),p1=p;
Point p2 =new Point(3.2,4.3);
System.out.println("p.equals(p1):"+p.equals(p1)
+"\tp.equals(p2):"+p.equals(p2));
}
}
Pour comparer les objets (pas leur réferenceces) "equals()" doit
être redéfini
convenablement!
9.8 Classes
et méthodes finales
Une classe déclarée final ne peut plus être
dérivée. But - liaisons statiques, optimization.
Une méthode déclarée final ne peut pas être
redéfinie dans une classe dérivée.
9.9 Les
classes abstraites
Une classe abstraite est une classe
qui ne permet
pas d’instancier des objets. Elle ne peut servir que de classe de
base pour une
dérivation.
Dans une classe abstraite, on peut
trouver
classiquement des méthodes et des variables membres, dont
héritera toute classe
dérivée. Mais on peut aussi trouver des méthodes
dites abstraites. Une méthode
abstraite ne fournit que la signature et le type de la valeur de retour.
-------------------------
abstract class A{
…
public void fx() {
..... }
// fx() est définie dans A
public abstract
void fa(int n) ; // fa() – méthode
abstraite, elle n’est pas définie dans A
// on
n’en a fourni que l’en-tête(pas de corps de la
méthode)
}
-------------------------------
A va ;
// OK : va n’est qu’une
référence sur un objet de type A ou dérivé
va = new A(...) ;
// erreur : pas d’instanciation d’objets
d’une classe abstraite
----------------------------
class B extends A{
public void fa(int
n) { ..... } // ici, on définit la méthode abstraite fa()
.....
}
A va = new B(...) ; // OK
-------------------
• Une méthode abstraite ne peut pas
être déclarée private, ce qui est logique puisque
sa vocation est d’être redéfinie dans une classe
dérivée.
• Dans l’en-tête d’une
méthode déclarée abstraite, les noms
d’arguments muets doivent figurer (bien qu’ils ne servent
à rien).
• Une classe dérivée d’une
classe abstraite n’est pas obligée de (re)définir
toutes les méthodes abstraites de sa classe de base (elle peut
même n’en redéfinir aucune). Dans ce cas, elle reste
simplement abstraite (il est quant même nécessaire de
mentionner abstract dans sa déclaration).
• Une classe dérivée d’une
classe non abstraite peut être déclarée abstraite
et contenir des méthodes abstraites.
• En générale dans la
programmation orientée objet, une classe abstraite est une
classe de quelle on ne peut pas instancier des variables. Une
méthode abstraite est une méthode avec une
définition différée ou retardée pour
être fait dans une classe dérivée.
Exemple:
abstract public class Displayable {
abstract public
void dspl();
}
---------------------------------------
public class Two extends Displayable{
private double
value;
Two(double v){
this.value =v;
}
public void dspl(){
System.out.println("double value: "+value);
}
}
--------------------------------------------------
public class One extends Displayable{
private int value;
One(int v){
this.value =v;
}
public void dspl(){
System.out.println("int value: "+value);
}
}
-------------------------------------------
public class Test {
public static void
main(String arg[]){
Displayable tb[] = new Displayable[6];
for(int i =0;i<tb.length;i++){
double d;
if((d=Math.random())>0.5){
tb[i]=new One((int)(d*10));
}
else {
tb[i]=new Two(d*10);
}
}
for(int i=0;i<tb.length;i++){
tb[i].dspl();
}
}
}
9.10 Les
interfaces
Si l’on considère une classe abstraite
n’implantant aucune méthode et aucun variable de classe,
on aboutit à la notion
d’interface. En effet, une interface définit les
en-têtes d’un certain nombre
de méthodes, ainsi que des constantes.
• Une classe pourra hériter (mot
clé - impléments)
plusieurs
interfaces (alors qu’une classe ne pouvait hériter
(mot clé extends) qu'
une seule classe abstraite).
• Les interfaces pourront se dériver (mot
clé extends).
• On pourra utiliser des
références de type interface.
9.10.1
Définition
public interface Int {
void fx(int n) ;
//
en-tête d’une méthode fx() public abstract
facultatifs
void gx() ;
// en-tête d’une méthode gx()
public abstract facultatifs
}
9.10.2
Implémentation d’une interface
class A implements Int {
// A doit
(re)définir les méthodes f x() et gx() prévues
dans l’interface Int
}
-----------------------------------------------------
class A implements Int, Int1, Int2 {
// A doit
(re)définir les méthodes prévues dans les
interface Int, Int1 et Int2
}
9.10.3 Les
références de type interface
public interface Int { .....}
.....
Int intrf ; // intrf peut
référencier un objet d’une classe
implémentant l’interface I
----------------------------
class A implements Int { ..... }
.....
Int intrf = new A(...) ; // OK
9.10.4
Interfaces et constantes
Une interface peut renfermer des constantes qui sont accessibles
à toutes les classes implémentant l’interface.
Ces constantes sont automatiquement considérées comme si
elles avaient été déclarées public, static et final.
9.10.5
Dérivation d’une interface
interface Int1 {
void fx(int n) ;
static final int
MAXI = 100 ;
}
interface Int2 extends Int1 {
void gx() ;
static final int
MINI = 20 ;
}
En fait, la définition de Int2 est équivalente
à :
interface Int2{
void f(int n) ;
void g() ;
static final int
MAXI = 100 ;
static final int
MINI = 20 ;
}
La possibilité partager la définition en deux augmente la
flexibilité du programme.
9.10.6 Exemple
interface Modif {
void zoom();
}
interface Displayable extends Modif{
void dspl();
}
---------------------------------------
public class Two implements
Displayable{
private double
value;
Two(double v){
this.value =v;
}
public void dspl(){
System.out.println("double value: "+value);
}
public void zoom(){
value/=2;
}
}
--------------------------------------------------
public class One implements
Displayable{
private int value;
One(int v){
this.value =v;
}
public void dspl(){
System.out.println("int value: "+value);
}
public void zoom(){
value*=2;
}
}
-------------------------------------------
public class Test {
public static void
main(String arg[]){
Displayable tb[] = new Displayable[6];
for(int i =0;i<tb.length;i++){
double d;
if((d=Math.random())>0.5){
tb[i]=new One((int)(d*10));
}
else {
tb[i]=new Two(d*10);
}
}
for(int i=0;i<tb.length;i++){
tb[i].dspl();
tb[i].zoom();
tb[i].dspl();
System.out.println();
}
}
}