9.    L’héritage

Définir une nouvelle classe, dite classe dérivée, à partir d’une classe existante dite classe de base


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();       
    }
}

9.2    Les variables avec le même nom

package deriv1;
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;
    }
}
-------------------------------------------------
package deriv1;
public class PointC extends Point{
    private byte col=3;
    private String name ="color point";
    public void dspl(){
        System.out.println("point col:("+x+","+y+
                                        ","+col+")");
        System.out.print(""+name);        //name locale
        System.out.println("   "+super.name);
    }
    public void inc(){
        x++; y++;
    }
}


-----------------------------------------------------------------

package deriv1;
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");
        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");
        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();
    }
}

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




        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 (overriding)

Une classe dérivée pourra surcharger une ou plusieurs méthodes d’une classe ascendante.

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();
    }
}
--------------------------------------------------------------

9.5.2    Redéfinition de méthodes

La redéfinition (en anglais overloading) 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.

Le mot clé super

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(){
        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[]){
        Point p1 = new Point(1.4,2.6);
        System.out.println(""+p1);
        PointC pc = new PointC(1.3,2.4,(byte)2);
        System.out.println(""+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.

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.

9.6.1    Les bases


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

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 de quelque façon que ce soit.

-------------------------------------------

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

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

pc2 = (PointC) p ;        // accepté en compilation, ClassCastException possible
                                    // lors execution

operateur    instanceof  pour verifier
p instanceof PointC


9.7    La superclasse Object

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()

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()

toString() 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 "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

abstract class A{
    .....
}
-------------------------
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.

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

•    Une classe pourra implémenter plusieurs interfaces (alors qu’une classe ne pouvait dériver que d’une seule classe abstraite).
•    Les interfaces pourront se dériver.
•    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 ; // i est une référence à 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();
        }
    }
}