Dans le chapitre précédent, nous nous sommes intéressés à la notion de type complexe, et vous avez écrit et instancié votre première classe ! Dans ce chapitre, nous aborderons des concepts très importants de la programmation orientée objet en spécialisant nos classes.
Spécialisez une classe grâce à l’héritage
Reprenons notre exemple d’un livre défini par un titre et un auteur. Quand on y pense, un CD musical n’a-t-il pas également un titre et un auteur ? Ce socle commun peut être mis dans une classe média, en généralisant. Bien sûr, un livre possède des éléments particuliers (nombre de pages, par exemple), et un CD également (durée).
Dans l’exemple ci-dessous, nous avons une classe mère FigureGeometrique
que nous allons spécialiser en Carre
. Le mot clé est extends
. On peut dire que la classe Carre
étend la classe FigureGeometrique
.
Commençons par définir la classe mère FigureGeometrique
:
public class FigureGeometrique {
private int x;
private int y;
public void moveTo(int newX, int newY) {
this.x = newX;
this.y = newY;
}
}
Ensuite, nous créons une classe fille Carre
:
public class Carre extends FigureGeometrique {
private long cote;
public long getCote() {
return cote;
}
public long getPerimetre(){
return 4*cote;
}
}
Avec la classe Carre,
nous récupérons automatiquement tous les attributs de la classe de mère FigureGeometrique
. Et nous lui avons ajouté un nouvel attribut de classe et 2 nouvelles méthodes participant ainsi à la spécialisation.
Notons également que lorsqu’on fait de l’héritage, tous les champs sont hérités. Ils peuvent être manipulés si leur accessibilité le permet (nous avons abordé ce concept dans le chapitre 5 de la première partie).
Dans notre exemple, nous pourrions créer une classe intermédiaire nommée FigureGeometriqueAvec4Cotes
et définir notre héritage de cette manière :
FigureGeometrique → FigureGeometriqueAvec4Cotes → Carre ;
FigureGeometrique → FigureGeometriqueAvec4Cotes → Rectangle ;
FigureGeometrique → FigureGeometriqueAvec4Cotes → Losange ;
…
À quoi correspondent les objets de la classe dérivée ?
Tout objet d'une classe dérivée est considéré comme étant avant tout un objet de la classe de base :
un
Carre
est avant tout uneFigureGeometrique
;tout objet d'une classe dérivée cumule les champs dérivés dans la classe de base avec ceux définis dans sa propre classe :
public class Test {
public static void main(String[] args) {
FigureGeo figure = new FigureGeometrique();
figure.moveTo(1, 1);
Carre carre = new Carre();
carre.moveTo(2, 2);
}
}
Initialisez les attributs hérités
L’initialisation des attributs d’une instance de classe se fait dans le constructeur de la classe, vous vous souvenez ? Lorsque des attributs sont hérités d’une classe mère, il est tout à fait possible de les initialiser dans le constructeur de la classe fille en appelant le constructeur de la classe parent. On voit ça ensemble ?
Commençons par créer un constructeur dans la classe mère FigureGeometrique
:
class FigureGeometrique {
private int x;
private int y;
FigureGeometrique(int x, int y) {
this.x = x;
this.y = y;
}
}
Ensuite, utilisons celui-ci dans notre classe fille Carre :
class Carre extends FigureGeometrique {
long cote;
Carre(long cote, int x, int y){
//Appel du constructeur de la classe mère FigureGeometrique
super(x, y);
this.cote = cote;
}
}
En Java, pour appeler le constructeur de la classe mère depuis le constructeur de notre classe fille, nous utilisons la méthode super. Celle-ci fait directement référence à la méthode écrite dans la classe parent, ici le constructeur, mais nous verrons ça plus en détail avec le polymorphisme !
Redéfinissez une méthode de classe grâce au polymorphisme
Lorsque vous construisez une classe héritant d'une autre classe, vous avez la possibilité de redéfinir certaines méthodes de la classe mère. Il s'agit de remplacer le comportement de la fonction qui a été définie par la classe mère.
C’est le concept de polymorphisme. L’idée étant de pouvoir utiliser le même nom de méthode sur des objets différents. Et bien sûr, cela n’a de sens que si le comportement des méthodes est différent.
Redéfinissez une méthode de la classe parente
Considérons le code ci-dessous, considérons la méthode deplacer()
dans la classe mère Animal
:
class Animal {
void deplacer() {
System.out.println("Je me déplace");
}
Appliquons le principe de polymorphisme pour cette méthode dans les différentes classes filles Chien
, Oiseau
et Pigeon
:
class Chien extends Animal {
void deplacer() {
System.out.println("Je marche");
}
}
class Oiseau extends Animal {
void deplacer(){
System.out.println("Je vole");
}
}
class Pigeon extends Oiseau {
void deplacer() {
System.out.println("Je vole surtout en ville");
}
}
Sur toutes ces classes, vous pouvez donc appeler deplacer()
. Le polymorphisme permet alors d'appeler la méthode adéquate selon le type d'objet :
public class Test {
public static void main(String[] args) {
Animal a1 = new Animal();
Animal a2 = new Chien();
Animal a3 = new Pigeon();
a1.deplacer();
a2.deplacer();
a3.deplacer();
}
}
Même si le type de nos 3 variables sont les mêmes (Animal), les instances sont différentes et donc leurs comportements aussi. Dans notre exemple, l'exécution donne comme résultat :
Je me déplace Je marche Je vole surtout en ville
Appelez une méthode de la classe parente
La redéfinition des méthodes dans la classe fille remplace tout le code de la méthode mère. Parfois ce fonctionnement est idéal, parfois nous souhaitons quand même appeler le code de la classe mère tout en ajoutant autre chose dans la classe fille.
Dans notre exemple, imaginons que le chien aboie lorsqu’il se déplace.
Nous pouvons accéder à l’implémentation parente grâce au mot clé super, et appeler la méthode déplacer avant d’ajouter l’aboiement du chien :
class Chien extends Animal {
void deplacer() {
super.deplacer();
System.out.println("ouaf ouaf");
}
Attention, dans le cas d’un héritage multiple, il est seulement possible d'accéder à l’implémentation de la classe parente, pas plus ! Dans notre exemple, si nous créons une nouvelle classe fille Caniche qui étend de Chien, nous n’accéderons dans celle-ci avec super qu’à l’implémentation de la classe Chien et non de la classe Animal.
Utilisez les annotations
Il existe en Java des types spéciaux commençant par@
, appelés annotations. Ils servent à préciser le comportement d’une classe, d’une méthode, d’un attribut ou même d’une variable. Les annotations donnent des informations au compilateur pour l'exécution du code de notre programme.
En Java, l'une des annotations les plus connues et utilisées est @Override
. Elle est utilisée en complément du polymorphisme pour indiquer que la méthode annotée est une redéfinition d’une méthode de la classe mère. Si l’annotation est présente sur une méthode, le compilateur va vérifier que la signature de la méthode est bien identique à celle de la méthode dans la classe mère.
Reprenons une nouvelle fois notre exemple et voyons comment utiliser l'annotation @Override
:
class Animal {
void deplacer() {
System.out.println("Je me déplace");
}
class Chien extends Animal {
@Override
void deplacer() {
System.out.println("Je marche");
}
}
En résumé
L’héritage est un concept fondamental en Java qui permet de réutiliser du code de la classe mère.
Le polymorphisme permet de "surcharger" les méthodes de la classe mère pour redéfinir leurs comportements sans changer leur signature.
Dans le prochain chapitre, nous verrons comment stocker et manipuler beaucoup de données avec les collections. Nous verrons qu’il existe beaucoup de types de collections différents pour classer les éléments.