Java 07: Héritage
La programmation orientée objet te permet déjà de réaliser une abstraction sur ton code : réfléchir en termes d'état et de comportements, plutôt que simplement à un enchaînement d'instructions.
Nous avons vu en exemple Duck, qui possédait comme état name, age et swimming, et comme comportements la méthode instanciée nameAndAge et la méthode statique quack.
Imagine que tu veuilles créer la classe Eagle. Si l'on réfléchit en terme d'abstraction, un aigle, et un canard, ça reste relativement proches. Mais un aigle ne nage pas et ne fait pas "coin coin".
L'idée, c'est de créer la classe Eagle, mais comme pas mal de ses états et comportements sont partagés avec Duck, comment éviter de dupliquer tout le code ?
C'est là que l'héritage entre en jeu !
Objectifs
- Connaître les notions liées à l'héritage
Faire abstraction
1. Classe abstraite
Tu possèdes la classe Duck et tu désires généraliser son fonctionnement avec une classe Bird. En effet, un oiseau aura la plupart des comportements d'un canard, par exemple le fait d'avoir un nom et un âge.
Créons donc la classe Bird :
public abstract class Bird {
// attributs
private String name;
private int age;
// constructeurs
public Bird(String name) {
this.name = name;
this.age = 0;
}
// accesseurs et mutateurs
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
Si tu es observateur, tu as peut-être remarqué le mot-clé abstract dans la déclaration de classe.
En effet, un oiseau n'est pas un concept tangible : tu pourras caresser un canard (chacun son truc), mais pas un oiseau. De la même façon, tu pourras piloter une voiture, mais pas un véhicule.
Une classe abstraite ne peut pas avoir d'instance. Il te sera donc impossible de créer un objet Bird.
Je te vois déjà avec un regard interrogateur dire : "Hein, pas d'instance ?!".
Revoyons notre exemple de la quête sur la programmation orientée objet : créer un nouvel objet de la classe Duck revient à dire que l'on crée une instance de cette classe.
Duck donald = new Duck("Donald", 84);
Un espace mémoire de notre canard est créé, avec l’initialisation des valeurs des attributs. De plus, une référence donald pointe vers cet espace mémoire.

La même chose te sera impossible avec une classe abstraite.
Quel intérêt d'avoir une classe abstraite dans ce cas ? Y créer des comportements qui seront hérités par des classes enfants.
2. Méthode abstraite
Une méthode abstraite est une méthode qui n'a pas d'implémentation.
Je retrouve de nouveau ce visage interloqué ! Ne pas avoir d'implémentation veut dire que la méthode n'aura pas de corps, mais juste une signature.
Imagine un oiseau ! Il aura un chant différent selon sa race : canard, aigle, corbeau...
Quel que soit l'oiseau, il aura un chant, mais tu ne peux pas définir lequel, à moins de savoir quel est précisément cet oiseau.
Ici, dans notre classe Bird, nous allons définir une méthode abstraite sing :
public abstract String sing();
Comme tu le remarques, pas d'accolade pour y définir un contenu, car ce sont les classes héritées de Bird qui devront définir le comportement de la méthode.
Cela fait plusieurs fois que nous parlons de classes héritées, sans expliquer ce que c'est. Voyons cela dans la section suivante !
Héritage et polymorphisme
1. Parent et enfants
Tu as ta classe abstraite Bird. Nous allons pouvoir l'utiliser comme classe parente et faire que notre nouvelle classe Eagle en hérite.
L'héritage, en programmation orientée objet, veut dire qu'une classe enfant va avoir le droit d'utiliser les états et comportements de sa classe parente.
Par exemple, tu peux créer une classe abstraite Vehicle qui aura pour attributs une marque et un kilométrage. Tu pourras ensuite créer des classes enfants Boat et Car qui en hériteront.
Revenons avec notre classe Bird. Imagines que tu crées une classe Eagle qui hérite de cette classe, tu pourras faire le code suivant :
public class Eagle extends Bird {
}
Tu remarques le mot-clé extends. C'est lui qui gère l'héritage.
Une classe ne peut hériter que d'une seule autre classe.
Telle quelle, la classe ne pourra pas passer la compilation pour deux raisons :
-
Elle n'a pas de constructeur
-
Elle n'a pas d'implémentation de la méthode
sing
2. Constructeur et super
La classe Eagle n'est pas abstraite, elle peut donc être instanciée. Il faut donc lui préciser un constructeur. Cependant, elle hérite de Bird, qui possède elle-même un constructeur, qui initialise name avec la valeur de l'argument et age à 0.
Il serait intéressant de récupérer ce constructeur plutôt que d'en recréer un de zéro. Voici comment tu vas faire cela :
public Eagle(String name) {
super(name);
}
Ce constructeur n'a pas le même contenu qu'un constructeur classique : tu as remarqué l'utilisation du mot-clé super !
Il est utilisé pour appeler la méthode telle qu'elle a été définie dans le parent ! Ici, cela revient à appeler le contenu du constructeur du parent, et donc d'instancier un objet Eagle et d'associer la valeur de l'argument à son attribut name, et la valeur 0 à age.
L'avantage est donc d'éviter la duplication de code. Mais si Eagle était totalement identique à la classe parente, il n'y aurait aucun intérêt à l'avoir créé.
Tu vas donc apprendre à étendre les états et comportements d'une classe héritée.
3. Implémentation des méthodes abstraites
La méthode abstraite sing dans Bird attend d'avoir une implémentation dans chaque classe héritée. Comme dit auparavant, toute race d'oiseau aura un chant.
Pour réaliser cette implémentation, il te faudra donc ajouter la méthode sing et son corps de méthode de la façon suivante :
@Override
public String sing() {
return "Screech!";
}
L'annotation @Override n'est pas obligatoire. Elle est là pour signifier au développeur que la méthode est héritée et modifiée.
On appelle polymorphisme le fait de redéfinir une méthode héritée.
Extension
La notion d'héritage se traduit par le fait d'étendre la classe parente, d'où l'utilisation du mot-clé extends. Cela veut dire qu'il est possible de modifier ou d'ajouter des choses dans une classe enfant .
Prenons le cas de notre aigle. Tu veux lui ajouter un attribut flying qui définit s'il est en train de voler ou non, tout en gardant les attributs name et age.
Tu vas pouvoir modifier la classe de la façon suivante :
public class Eagle extends Bird {
private boolean flying;
public Eagle(String name) {
super(name);
}
@Override
public String sing() {
return "Screech!";
}
public boolean isFlying() {
return flying;
}
public void setFlying(boolean flying) {
this.flying = flying;
}
}
Comme tu peux le voir, seul le nouvel attribut a été ajouté, et le constructeur a été modifié pour initialiser l'attribut flying après l'appel de super, qui appelle le constructeur de Bird.
Tu peux modifier le constructeur de la classe en initialisant l'attribut flying à false :
public Eagle(String name) {
super(name);
this.flying = false;
}
Récapitulatif
Cela te fait encore une tonne de notions à aborder. Tu vas pouvoir les tester en récupérant le dépôt git donné en ressource.
Il contient quatre classes :
-
Bird, classe abstraite -
Eagle, classe héritée deBird -
Duck, classe héritée deBird, que tu avais vue précédemment et qui a été modifiée pour les besoins de la quête -
Nature, que tu compileras et exécuteras, afin de voir en détail :
la création d'instances de deux classes héritées d'une classe abstraitela création de références vers ces instancesl'appel de méthodes étendues par des classes héritéesla création et le parcours d'une ArrayList, contenant des éléments de classes héritant d'un même parent
Ensuite, ouvre ces classes pour regarder comment tout ça est organisé. N'hésite pas à les modifier afin d'ajouter de nouveaux attributs, constructeurs, getters, setters et méthodes !
Challenge
Go Autoboat !
Afin de t'exercer à l'héritage, tu vas créer les classes suivantes :
- Crée une classe abstraite
Vehicle - Ajoute les attributs
String brandetint kilometers - Crée un constructeur permettant d'initialiser
brandetkilometers - Ajoute des getters et setters pour tous les attributs, en respectant les conventions
- Crée une classe
Carqui étend la classeVehicle - Crée une classe
Boatqui étend la classeVehicle - Ajoute dans
Vehiclela méthode abstraitepublic String doStuff() - Ajoute dans
CaretBoatune implémentation du corps de la méthodedoStuff(). PourCar, tu devras retourner "Je suis {brand} et je fais vroum vroum !" et pourBoat: "Je suis {brand} et je fais glou glou !" - Crée une classe
Hangarqui possédera une méthode main() - Ajoute des références vers des instances des classes
CaretBoatdans la méthode main() deHangar - Pour chacune des références, affiche dans le terminal le résultat de l'appel de la méthode
doStuff() - Compile et teste
Hangar: quand tout fonctionne, crée un dépôt git et envoie le tout sur GitHub - Partage le lien du dépôt GitHub en solution
Exemple de résultat attendu lors de l'exécution de Hangar :
$ Je suis Clio et je fais vroum vroum !
$ Je suis Titanic et je fais glou glou !
Critères de validation
- Le dépôt GitHub contient bien les fichiers
Vehicle.java,Car.java,Boat.javaetHangar.java - Les classes respectent les conventions de la POO : attributs privés, utilisation du mot-clef this, getters et setters nommés correctement.
- Des références à des instances de la classe
CaretBoatsont présentes dans la classeHangar - La classe
Hangarse compile sans erreur et affiche le résultat attendu dans le terminal
Ressources partagées sur cette quête
- bien comprendre l'héritage en java https://www.javatpoint.com/inheritance-in-java