1 Notion fondamentale en POO : l'héritage
2 Construction et initalisation des objets dérivés
3 Redéfinition et surdéfinition de membres
4 Polymorphisme et transtypage
5 Super Classe Object
6 Classes Abstraites
7 Interface
Les classes sont généralement organisées de manière hiérarchique. Le programmeur organise les classes dont il a besoin de manière hiérarchique
ou bien il se rattache aux hiérarchies de l'API.
On peut définir une classe (classe dérivée, classe fille...) à partir d'une autre (classe de base, class mère etc...).
La classe dérivée hérite des attributs et des méthodes de sa classe de base, et peut ajouter des attributs et des comportements, voire
redéfinir une partie des comportements de la classe mère. Le mécanisme de l'héritage est fondamental : on dispose d'un certain
nombre de classes dans l'API, il est possible de définir un programme en utilisant des dérivatifs des classes de l'API et sans avoir beaucoup à
coder.
On suppose disposer d'une classe Point :
public class Point { public void initialise(int abs,int ord) { x=abs; y=ord; } public void deplace(int dx,int dy) { x+=dx; y+=dy; } public void affiche() { System.out.println("Point en "+x+" "+y); } private int x,y; }
public class PointCol extends Point { public void colore(byte couleur) { this.couleur=couleur; } private byte couleur; }
PointCol pt=new PointCol(); pt.deplace(5,6); pt.affiche();
Dans l'exemple précédent, les classes ont été construites sans constructeurs. Ce cas ne pose pas de difficultés. Le constructeur défini dans la classe dérivée peut ne pas faire appel au constructeur de la classe au dessus. Le constructeur peut également faire réference au constructeur de la classe mère. Dans ce cas, on utilise le mot clé super. et ce doit être la première instruction du constructeur de la classe dérivée
public class Point { public Point(int x,int y) { ... } } public class PointCol { public PointCol(int x,int y,int color) { super(x,y); this.couleur=color; } }
Dans le chapitre 3, nous avons vu la notion de surdéfinition à l'intérieur d'une même classe. La surdéfinition concerne des méthodes de
même nom mais de signatures différentes (signature : nom+attributs). Une classe dérivée peut sur-définir les méthodes de sa classe de
base. Il devient en plus possible de rédéfinir une méthode de même signature dans une classe dérivée. Soit un premier cas :
public class Point { ... public void affiche() { System.out.prinltn("Point : "+x+" "+y); } private int x,y; } /*Autre fichier : */ public PointCol extends Point { ... } /*Autre fichier avec le main : */ public static void main(String[] args) { PointCol pc=new PointCol(); Point p=new point(); ... }
public class Point { ... public void affiche() { System.out.prinltn("Point : "+x+" "+y); } private int x,y; } /*Autre fichier : */ public PointCol extends Point { ... private byte color; public void affiche() { System.out.prinltn("Point : "+x+" "+y+" "+color); } } /*Autre fichier avec le main : */ public static void main(String[] args) { PointCol pc=new PointCol(); Point p=new point(); ... }
Le polymorphisme consiste dans le fait de manipuler des objets sans en connaître tout à fait le type. Par exemple, il est possible de manipuler
une collection d'élements Point sans savoir si ce sont des objets Point ou PointCol et appeller affiche(). Un objet de type PointCol est toujours
manipulable comme un objet de type Point.
Par ailleurs, ce genre d'affectation est possible :
Point p=new PointCol(5,6,(byte)2);.
PointCol pC=new PointCol(5,6,(byte)3); Point p=pC;
Point p=new Point(); PointCol pC=p;
PointCol pC=(PointCol)tab[1];
if(tab[1] instanceof PointNoir) { ((PointNoir)tab[1]).affichagePointNoir(); } else if(tab[1] instanceof PointCol) { ((PointCol)tab[1]).affichagePointCol(); } else { tab[1].affichage(); }
En Java, il existe une hiérarchie explicite qu'on retrouve à travers le mot clé extends sur la ligne public class PointCol extends Point.
Il existe aussi une hiérarchie implicite et toute classe dérive d'une super-class Object, ie tout objet peut utiliser les méthodes de la
class Object. De fait, le code suivant ne pose aucun problème d'exécution ou de compilation :
Point p=new Point(...); PointCol pC=new Point(...); Facture fac=new Facture(...); Object ob; /*L'ensemble des instructions suivantes ne posent pas de pb : */ ob=p; ob=pC; ob=fac;
Point p=new Point(); String st=p.toString(); System.out.println("Ce que donne la methode toString() : "+st); /*Ces deux dernieres instructions sont en fait équivalentes à :*/ System.out.println("Ce que donne la methode toString() : "+p);
public class Point{ int abs; int ord; public Point(int x,int y) { abs=x; ord=y; } public String toString() { return (abs+" "+ord); } }
Certaines classes sont particulières au sein des hiérarchies : les classes abstraites. Ce type de classe ne supporte pas l'instanciation. Elle sont définies
en introduisant le mot-clé abstract dans la définition de la classe :
public abstract class ...
Ces classes ne servent que pour la dérivation de classe. On peut trouver des attributs et des méthodes qui seront utilisables par des classes dérivées.
Certaines méthodes sont abstraites : public abstract String getName() : les classes dérivées devront obligatoirement définir ces méthodes
si elles ne sont pas abstraites.
Dès qu'une classe contient au moins une méthode abstract, elle est abstract, sans qu'il faille le préciser dans la ligne de déclaration de la classe.
Quel intérêt des classes abstraites ?
On peut utiliser une classe abstraite pour stocker toutes les fonctionnalités dont on souhaite disposer pour toutes les classes descendantes :
public abstract class X { public abstract void f();//ici f n'est pas encore définie }
void algo(X x) { ... x.f(); ... }
/*un premier fichier : Affichable.java*/ public abstract class Affichable { abstract public void affiche(); } /*Un deuxième fichier : Entier.java*/ public class Entier extends Affichable { public Entier(int n) { valeur=n; } public void affiche() { System.out.println("Je suis un entier de valeur "+valeur); } private int valeur; } /*Un troisième fichier : Reel.java*/ public class Reel extends Affichable { public Reel(double x) { valeur=x; } public void affiche() { System.out.println("Je suis un réel de valeur "+valeur); } private double valeur; } /*Une classe avec un main pour le lancement : */ public class TestAbstractCl { public static void main(String[] args) { Affichable[] tab; tab=new Affichable[3]; tab[0]=new Entier(25); tab[1]=new Reel(1.25); tab[2]=new Entier(50); int i; for(i=0;i<3;i++) tab[i].affiche(); } }
On peut considérer une classe abstraite n'implémentant aucune méthode et aucun champ : il s'agit d'une interface. La notion d'interface a un certain nombre d'avantages :
public interface I { void f(int n); void g(); }
public class A implements I { //A doit alors définir une méthode f et une méthode g }
public interface I1 { void f(int n); } public interface I2 { void g(); } public class A implements I1,I2 { //A doit implementer les méthodes des deux interfaces : g() et f(int) }
I1=new A();