f. voisin : introduction à java 1 introduction à java - lhéritage - frédéric voisin fiifo - «...
TRANSCRIPT
F. Voisin : Introduction à Java 1
Introduction à Java
- l’héritage -
Frédéric VOISIN
FIIFO - « Remise à Niveau »
F. Voisin : Introduction à Java 2
Classes et Héritage
Définir une nouvelle classe par « extension » ou « spécialisation » d’une classe existante, en n’en donnant que ce qui diffère.
class SousClasse extends SuperClasse
les instances de SousClasse sont vues comme étant aussi des instances de SuperClasse.
+ spécifique,+ riche(sous-classe)
+ générale(classe)
SuperClasse
SousClasse
F. Voisin : Introduction à Java 3
Classe et Héritage (suite)
Une sous-classe peut
ajouter de nouveaux attributs (la redéfinition ou l’oubli d’attributs est interdit, mais un attribut peut en masquer un autre dans la super-classe)
ajouter de nouvelles méthodes redéfinir (spécialiser) des méthodes de sa super-classe
On peut interdire
qu’un membre de la super-classe soit visible des sous-classes : Il est alors hérité mais pas
référençable à partir d’une méthode de la sous-classe.
Les membres référençables par les sous-classes sont ceux qui ont une visibilité public ou
protected.
que certaines méthodes soient redéfinies (final), que certaines classes soient dérivables (final)…
L’héritage est une relation transitive !
F. Voisin : Introduction à Java 4
Les hiérarchies de classes Java
En Java, on ne peut dériver via extends que d’au plus une classe. On parle alors d’héritage simple (par opposition à héritage multiple)
Si une classe n’a pas de super-classe explicite, elle dérive implicitement de la classe prédéfinie Object
Toute classe hérite donc, directement ou non, de Object qui fournit des services par défaut. Ces services par défaut peuvent (et souvent doivent) être redéfinis dans les sous-classes : public boolean equals(Object O) public String toString() protected Object clone()
F. Voisin : Introduction à Java 5
Un exemple de hiérarchie de classes
Carré …
FigureOrigine: contient(Point2D) dessiner()effacer() move(int dx, int dy)
Point2D x, y: double getX(), getY() move(int dx, int dy)
Rectangle
largeur, longueur: double
contient(Point2D) dessiner() effacer()
Cercle
rayon: double
contient(Point2D) dessiner() effacer()
Segment Opposé:
contient(Point2D) dessiner() effacer()
F. Voisin : Introduction à Java 6
Héritage et typage
Partout où on attend une Figure, on peut utiliser un Cercle :public void f(Figure fig) {
Cercle c = new Cercle(…);
fig.move(12, 24); // correct, toujours défini
c.move(12, 24); // correct, toujours défini
fig = c; // correct, toujours défini, mais pour le compilateur
// fig référence toujours une Figure arbitraire !
fig.rayon = 12; // KO
c = fig; // KO ! que vaudrait c.rayon si fig était une figure arbitraire ?
}
f(new Cercle); f(new Rectangle); // OK !
Ces conversions implicites vers Figure sont acceptées silencieusement par le compilateur et ne peuvent pas échouer !
F. Voisin : Introduction à Java 7
Héritage et Liaison Dynamique de Fonctions
On se base sur le type réel de l’objet (et non pas son type apparent) pour savoir
quelle méthode exécuter (polymorphisme, ou liaison dynamique) :Figure fig; Point2D p = new Point2D(1,1);
Math.Random hasard = new Math.Random();
if (hasard.nextInt(2) != 0) // Pile ou face ?
fig = new Cercle(p, Math.Pi);
else fig = new Rectangle(p,1,1);
fig.dessiner(); // quelle dessiner() ?
Les méthodes peuvent s’appuyer sur les comportements redéfinis :
public move(double dx, double dy) { // dans Rectangle
this.effacer(); super.move(dx, dy); this.dessiner();
}
F. Voisin : Introduction à Java 8
Héritage et constructeurs
Les constructeurs ne sont pas hérités mais il existe un « protocole » pour effectuer les initialisations en cascade en remontant la hiérarchie de classes
dans Figure: protected Figure(Point p) { origine = p; }
dans Rectangle:public Rectangle(Point p, double long, double larg) {
super(p); // Première instruction !
longueur = long; largeur = larg;
}
« Méthodologiquement » un constructeur ne devrait s’occuper que des attributs définis à son niveau.
F. Voisin : Introduction à Java 9
Héritage et typage: exemple 2
ObjGraphique
dessiner()
Diagramme
ObjGraphique[] elements
dessiner()ajouter(ObjGraphique)supprimer(ObjGraphique)
Figure
Point2D Origine
dessiner()
Cercle
…
dessiner()
Rectangle
…
dessiner()
F. Voisin : Introduction à Java 10
Exemple 2 (suite)
Diagramme d = new Diagramme(); Point2D p = new Point2D(0,0);d.ajouter(new Cercle(p, Math.Pi));d.ajouter(new Rectangle(p, 3.0, 5.0));
public void dessiner() { // dans Diagramme for(int i = 0; i < elements.length; i++)
elements[i].dessiner(); // quelle dessiner() ??}public void supprimer(ObjGraphique p) { for(int i = 0; i < elements.length; i++)
if (elements[i].equals(p)) … // quelle equals() ??} La classe ObjGraphique ne sert qu’à donner un chapeau (type) commun
aux deux sous-classes.
Dynamiquement, il n’y a que des instances des classes concrètes !
F. Voisin : Introduction à Java 11
Héritage et typage (suite)
On a parfois besoin de la conversion de la super-classe vers la sous-classe : cela doit être explicite : c = (Cercle) fig;
cela échoue à la compilation si la conversion « n’a pas de sens » cela peut échouer à l’exécution (levée d’une exception) : si fig ne contient pas
à cet instant une instance (au sens large) de Cercle.
Rectangle monR = new Rectangle();
Figure fig; Cercle monC;
monC = (Cercle) monR; // KO à la compilation car désespéré !
fig = monR; // toujours OK
monC = (Cercle) fig; // OK à la compilation, KO à l’exécution
le compilateur ajoute donc le code qui fera la vérification à l’exécution.
F. Voisin : Introduction à Java 12
Héritage et Typage : exemple 1
C’est notamment le cas avec les classes de la hiérarchie Collection qui implémente différentes structures de données.
dans ArrayList:
public void add(Objet e);
public Object get(int i);
dans Employe:
private static ArrayList listeEmployes = new ArrayList() ;
...
… listeEmployes.add(e); … System.out.println(listeEmployes.get(i).toString());
:-( a priori get renvoie (statiquement) une instance de Object
;-| pourquoi cela compile-t-il ? Pourquoi cela marche-t-il ?
F. Voisin : Introduction à Java 13
Héritage et Typage : exemple 1(suite)
Si on voulait réellement récupérer l’instance de Employé ??Employe e = listeEmployes.get(i); // KO !
Employe e = (Employe) listeEmployes.get(i); // OK ?
listeEmployes.get(i).imprime() // KO
((Employe) listeEmployes.get(i)).imprime() // OK ?
Attention : si listeEmployes contient autre chose que des instances de Employes (ce n’est pas add que ça dérangerait !!!)
Il faut éviter de tester le type des objets et utiliser la redéfinition de méthodes :
redéfinition de toString()et non pas définition de imprime()
F. Voisin : Introduction à Java 14
Exemple 3 : Représenter les règles suivantes qui régissent une partie des différents statuts possibles d'une société...
SNC (Société en nom collectif) Capital social : aucun capital requis Nombre d'associés : 2 minimum, pas de maximum Cession des parts : nécessite l'unanimité des associés
SARL (Société à responsabilité limitée) Capital social : 7 500 euros Nombre d'associés : 2 au minimum, 50 au maximum Cession des parts : librement cessibles aux associés, conjoints, ascendants et descendants; accord des 3/4 des associés sinon
SA (Société anonyme) Capital social : 37 000 euros (225 000 euros, en cas d'appel public à l'épargne) Nombre d'associés : 7 minimum, pas de maximum Cession des parts : entièrement libre
F. Voisin : Introduction à Java 15
Associé
estProche(Associé);
PersonneMorale…
PersonnePhysique…
Société
Associés[] lesAssociés;
int capital;
nom, siège, objet, durée: …
protected Société(…);
cession(Associé, Associé);
addAssocié(Associé);
protected cessionPossible(Associé, Associé);
SNC
SNC(Associé[], int, …);
SNC(Associé[], …);
SARL
SARL(Associé[], capital, …);
cessionPossible(Associé, Associé, ...);
addAssocié(Associé);
SA
SA(Associé[], int, ...);
SAE
SAE(Associé[], int, …);
Association
F. Voisin : Introduction à Java 16
Il n’existe pas de société sans statut spécifique. On ne veut donc pas pouvoir créer d’instance de Société (classe « abstraite »).
Le même problème se posait avec Figure La classe Société ne sert qu’à factoriser la description :
Attributs communs Méthodes publiques avec « comportement par défaut » Méthodes publiques qui doivent être redéfinies par les sous-classes (méthodes
« abstraites ») Méthodes auxiliaires pour faciliter la réalisation des sous-classes (méthodes avec
visibilité protected ou moins)
Les sous-classes ajoutent des informations ou redéfinissent des comportements De même, un « associé » n’existe pas vraiment ! C’est soit une personne morale,
soit une personne physique ! Associé et PersonneMorale sont abstraites.
Exemple 3 (suite)
F. Voisin : Introduction à Java 17
Exercice
Précisez les comportements des différentes méthodes (en pseudo-code)
F. Voisin : Introduction à Java 18
D’autres types de statuts
La hiérarchie peut être beaucoup plus compliquée, selon la complexité du monde à modéliser !
Il n’est pas simple de modifier une hiérarchie a posteriori
Société
SociétéPersonnes SociétéCapitaux SociétéMixte
SARLSASNC Entr.Individuelle
…
F. Voisin : Introduction à Java 19
Héritage ou Composition
Il existe deux moyens de « réutiliser » une classe : par héritage (Cercle hérite de Figure) par composition : avoir comme attribut un objet d’une autre classe, comme Point2D qui
est utilisée dans Figure.
Les deux moyens ont des usages différents !
Autre exemple: définition de « points colorés » :
class PointColore {
Point2D lePoint;
Couleur laCouleur;
…
}
class PointColore
extends Point2D {
Couleur laCouleur;
…
}
???
F. Voisin : Introduction à Java 20
Héritage ou Composition (suite)
Héritage : relation « est une sorte de ».
La classe dérivée est vue comme une extension, ou comme une spécialisation de la super-classe. Elle offre de nouveaux comportements et/ou en redéfinit.
Tous les comportements définis dans la super-classe et « visibles » sont applicables aux instances de la sous-classe.
Super-classe et sous-classe doivent être sémantiquement et structurellement « compatibles ».
Héritage ? Seulement si on veut rendre accessibles toutes les méthodes (non protected) de la super-
classe les structures sont compatibles (nombre et type des champs)
F. Voisin : Introduction à Java 21
Héritage ou Composition (suite)
Composition : relation « est composée de » :
La classe réutilisée participe à l’implantation de la classe considérée mais les deux ne sont pas compatibles.
Les méthodes ne sont pas vraiment reliées. Pour « réutiliser », on délègue:
Figure.move(dx, dy) { origine.move(dx, dy); }F.getX() n’a pas de sens si F est une instance de Figure.
Les membres de la classe composée ne sont pas visibles a priori : par exemple Point2D devra avoir prévu des méthodes (publiques) d’accès aux
coordonnées de l ’origine.
F. Voisin : Introduction à Java 22
Héritage ou Composition (fin)
Exercice : A posteriori, quelles relations entre
Figure et Point ?
Figures / ObjetGraphique / Diagrammes ?
Point2D et Point3D ?
Point et PointColoré ?
Date et Durée ?
F. Voisin : Introduction à Java 23
Héritage, surcharge et redéfinition
une méthode de la sous-classe ne redéfinit (et masque) que la méthode de la super-classe de même profil :
public boolean equals(Point2D) { … } // dans Point2D
ne redéfinit pas et ne masque pas
public boolean equals(Object 0) { … } // dans Object
Selon le type (statique) du paramètre on obtiendra l’une ou l’autre :-(
Ne pas confondre surcharge et redéfinition de méthodes, ni aspect « statique » et « dynamique » : surcharge : plusieurs méthodes de même nom mais de profils différents
redéfinition : cas particulier de « surcharge » en conservant exactement le profil et en présence d’une relation d’héritage.
F. Voisin : Introduction à Java 24
Héritage, surcharge et redéfinition (suite)
A la compilation, on détermine la « famille » (i.e. ensemble des redéfinitions dans une hiérarchie donnée) de méthodes potentiellement appelables, compte-tenu de la surcharge
A l’exécution, la liaison dynamique appelle la « bonne » méthode dans la famille déterminée statiquement, selon la classe du receveur.
La liaison dynamique se fait donc « à profil constant », le profil ayant été déterminé à la compilation, selon le type des paramètres…
F. Voisin : Introduction à Java 25
Héritage, surcharge et redéfinition (exercice)
class Object
public boolean equals(Object P)
class Point2D
public boolean equals(Object P)
public boolean equals(Point2D P)
class PointColore
public boolean equals(Object P)
public boolean equals(Point2D P)
public boolean equals(PointColore P)
class A
public boolean equals(Object P)
public boolean equals(A monA)
Déterminez les « familles » de méthodes...
F. Voisin : Introduction à Java 26
static void t1(Object O1, Object O2) {… O1.equals(O2) … }
static void t2(Point2D P1, Point2D P2) {… P1.equals(P2) … }
Point2D P1 = new Point2D(1.0, 1.0);
Point2D P2 = new Point2D(1.0, 1.0);
PointColoré PC1 = new PointColore(1.0, 1.0, Couleur.Blanc);
PointColoré PC2 = new PointColore(1.0, 1.0, Couleur.Vert);
t1(P1, PC1);
t1(PC1, P1);
t2(P1, PC1);
t2(PC1, P1);
… P1.equals(P1)…
… PC1.equals(P1)…
t1(P1, P2);
t2(P1, P2);
t1(PC1, PC2);
t2(PC1, PC2);
Dans chaque cas, déterminez la méthode appelée dynamiquement …
Qu’en déduit-on ?
F. Voisin : Introduction à Java 27
Héritage, surcharge et redéfinition (exercice)
public class Object
public boolean equals(Object P)
public class Point2D
public boolean equals(Object P)
public class Point2DColore
public boolean equals(Object P)
public class A
public boolean equals(Object P)
Programmez chaque méthode de la famille...
F. Voisin : Introduction à Java 28
La visibilité « protected »
public class C {
protected int val;
}
public class C2 extends C {
public void f(C arg, C2 arg2) {
this.val = 1; // OK
C2.val = 1; // OK
C.val = 1; // KO (modulo les règles sur les paquetages)
}
}
Il restera un quatrième niveau de visibilité à voir !
F. Voisin : Introduction à Java 29
Les classes abstraites
abstract class C { abstract public void f(); // pas de corps pour f !} Classe abstraite = une classe dont on ne veut pas pouvoir créer d’instance !
Racine pour une hiérarchie dans laquelle les sous-classes apporteront leurs particularités
Permet de partager certaines descriptions :Variables d’instances (ou de classes) communesMéthodes définissant un comportement par défautMéthodes abstraites à redéfinir par les sous-classes
Méthode abstraite = une méthode dont on donne uniquement le profil et qui devra être redéfinie dans toute sous-classe instanciable
Toute classe qui comporte une méthode abstraite doit être déclarée abstraite
Toutes les méthodes d’une classe abstraite ne sont pas forcément abstraites Le compilateur interdit l’instanciation des classes abstraites.
F. Voisin : Introduction à Java 30
Classes abstraites: un exemple
Figure est abstraite : une figure n’existe pas sans une forme précise
contient et dessiner ne sont pas implémentables sans connaître la forme précise.
Par contre, move peut être implémentée directement dans Figure...
Segment Opposé: Point
contient(Point)dessiner()
Figure
Origine: Pointcontient(Point)dessiner()move(int x, int y)
Cercle Rayon: Réel
contient(Point)dessiner()
Rectangle
largeur, long: Réel
contient(Point)dessiner()
F. Voisin : Introduction à Java 31
La classe Figure en Java
abstract public class Figure {
protected Point2D origine;
public abstract void dessiner();
public abstract boolean contient(Point2D P);
public void move(int x, int y) { origine.move(dx, dy); }
}
public class Rectangle extends Figure {
public void dessiner() { ... }
public boolean contient(Point2D P) { ... }
...
}
F. Voisin : Introduction à Java 32
Résumé: les « marqueurs » dans les déclarations
static : distingue ce qui est relatif à une classe ou à ses instances
public, private, protected: modifie la visibilité par défaut
final : ce qui ne changera plus ! pour un attribut : une constante (connue à la compilation ou non)
static final int MAX = 100; final String nom = "Java"; final String nom; // devra être initialisé dans tous les constructeurs final MaClasse monObjet = new MaClasse(); // sens ??
pour un argument de méthode: argument non modifiable (voir ci-dessus) pour une méthode: elle ne peut plus être redéfinie dans une sous-classe pour une classe: elle ne peut pas être dérivée.
abstract : pour les classes et méthodes seulement
F. Voisin : Introduction à Java 33
Le modificateur final: exemple
class Compteur { // Nouvelle valeur à chaque appel à next static int v = 0; public static int next() { return ++v; }}
class Cfinal { final static int i2 = Compteur.next(); final int i3 = Compteur.next(); // type primitif final int i4; final MaClasse monC = new MaClasse(); // type référence public CFinal() { i4 = Compteur.next(); } public String toString() { ... } // à finir ! public void f (maClasse arg) { monC.incr(); // OK ou KO ? monC = arg; // OK ou KO ? }}
F. Voisin : Introduction à Java 34
Le modificateur final: exemple (suite)
class MaClasse { int v = 0; public int incr() { return ++v; } public String toString () { return "v: " + v;}}
public class TestFinal { public static void main(String[] args) { CFinal t1 = new CFinal(); CFinal t2 = new CFinal(); t1.toString(); t2.toString(); t1.monC.incr(); t1.toString(); t2.toString(); }}