Java est un langage orienté objet, cela signifie que tout en Java est constitué de classes et de leurs objets et obéit aux principes (paradigmes) de la POO (Programmation Orienté Objet).
Héritage en Java
L’héritage est un mécanisme par lequel la classe est autorisée à hériter des fonctionnalités (champs et méthodes) d’une autre classe. En termes simples, l’héritage signifie créer de nouvelles classes basées sur celles existantes.
On utilise le mot-clé :
class Fille extends Mere {
}Comment fonctionne l’héritage ?
L’héritage va du plus général vers le plus précis.
- La classe la plus générale s’appelle superclasse.
- Les classes plus précises s’appellent des sous-classes.
- Les sous-classes peuvent récupérer ou hériter des attributs et méthodes de la classe parent.
Info
La classe
java.lang.Objectest la super-classe de toutes les classes Java, directement ou indirectement.
Exemple
Classe la plus générale : Animal
C’est une classe abstraite (modèle général).
Tous les animaux :
- ont un nom
- ont un âge
- mangent
- boient
- font un bruit (mais on ne sait pas lequel)
abstract class Animal {
protected String nom;
protected int age;
Animal(String nom, int age) {
this.nom = nom;
this.age = age;
}
void manger() {
System.out.println(nom + " mange.");
}
void dormir() {
System.out.println(nom + " dort.");
}
abstract void faireDuBruit();
}faireDuBruit() est abstraite car chaque animal fait un bruit différent.
Classe plus précise : Chien
Un chien est un animal → extends Animal
- hérite de
nom,age - hérite de
manger()etdormir() - doit définir
faireDuBruit() - ajoute une nouvelle propriété
- peut ajouter de nouvelles méthodes
class Chien extends Animal {
private String race;
Chien(String nom, int age, String race) {
super(nom, age); // constructeur parent
this.race = race; // this = attribut de la classe Chien
}
@Override
void faireDuBruit() {
System.out.println(nom + " aboie.");
}
void jouer() {
System.out.println(nom + " joue avec une balle.");
}
@Override
void manger() {
super.manger(); // appelle la version Animal
System.out.println("Le chien mange des croquettes.");
}
}Ce que l’on a
super(nom, age)→ appelle le constructeur parentthis.race→ fait référence à l’attribut du chien@Override→ redéfinit une méthodesuper.manger()→ utilise le comportement du parent + ajoute du code
Warning
L’héritage doit représenter une relation “est-un” : la classe enfant est un type spécial de la classe parent.
Incorrect :
Animalhérite deChienCorrect :Chienhérite deAnimal
Pourquoi utiliser l’héritage ?
- Réutiliser du code
- Éviter la duplication
- Créer une hiérarchie logique
- Permettre le polymorphisme
Exemple :
class Animal {
void manger() {
System.out.println("L'animal mange");
}
}
class Chien extends Animal {
void aboyer() {
System.out.println("Le chien aboie");
}
}Le Chien hérite de manger().
Redéfinition de méthode (Override)
La redéfinition de méthode permet à une classe enfant de fournir sa propre version d’une méthode héritée de la classe parent.
L’override permet de modifier le comportement hérité d’une classe parent, et c’est la base du polymorphisme en Java.
On utilise l’annotation @Override.
Comment ça fonctionne ?
Quand une méthode est redéfinie :
- Même nom
- Même paramètres
- Type de retour compatible
- Visibilité identique ou plus large
La version exécutée dépend de l’objet réel → Polymorphisme dynamique (à l’exécution).
abstract class Animal {
abstract void faireDuBruit();
}
class Chien extends Animal {
@Override
void faireDuBruit() {
System.out.println("Le chien aboie");
}
}Même si la référence est Animal, c’est la version Chien qui s’exécute.
Pourquoi utiliser l’Override ?
- Spécialiser un comportement
- Adapter une méthode à un cas précis
- Permettre le polymorphisme
- Rendre le code extensible
Warning
On ne peut pas redéfinir une méthode
finalLes attributs ne sont jamais concernés par l’override
Objet courant this
Ce mot clé this permet de désigner l’objet dans lequel on se trouve, c’est-à-dire que lorsque l’on désire faire référence dans une fonction membre à l’objet dans lequel elle se trouve, on utilise this.
L’objet courant this est en réalité une variable système qui permet de désigner l’objet courant. Il permet d’éviter les ambiguïtés et d’appeler un autre constructeur.
Accéder aux attributs de l’objet
On utilise surtout quand un paramètre a le même nom qu’un attribut.
class Animal {
String nom;
Animal(String nom) {
this.nom = nom; // this.nom = attribut / nom = paramètre
}
}Sans this, Java ne saurait pas faire la différence.
Appeler un autre constructeur → this()
Permet d’appeler un constructeur de la même classe.
class Animal {
String nom;
int age;
Animal(String nom) {
this(nom, 0); // appelle l'autre constructeur
}
Animal(String nom, int age) {
this.nom = nom;
this.age = age;
}
}
this()doit toujours être la première ligne du constructeur.
Classe parent super()
Le mot-clé super en Java est utilisé pour faire référence à l’objet parent immédiat de la classe. Il est généralement utilisé pour accéder aux méthodes et aux constructeurs de la classe mère, ce qui permet à une sous-classe d’hériter et de réutiliser les fonctionnalités de sa superclasse.
Appeler le constructeur parent → super()
class Animal {
Animal(String nom) {
System.out.println("Nom : " + nom);
}
}
class Chien extends Animal {
Chien(String nom) {
super(nom); // appel du constructeur parent
}
}
super()doit être la première instruction du constructeur.
Polymorphisme
Le polymorphisme est un principe fondamental de la POO.
Il permet de manipuler différents objets à travers un même type parent, tout en gardant leur comportement spécifique.
| Un même type peut prendre plusieurs formes
Concrètement, une variable de type parent peut contenir un objet d’une classe enfant.
Comment utiliser le polymorphisme ?
En Java, le polymorphisme repose sur :
- L’ Héritage en Java (
extends) - La Redéfinition de méthode (
Override) - Le typage par référence parent
Il existe deux types de polymorphisme en Java :
Polymorphisme statique (à la compilation)
- Surcharge de méthode (même nom, paramètres différents)
- Décidé à la compilation
Polymorphisme dynamique (à l’exécution)
- Redéfinition de méthode (
@Override) - Décidé à l’exécution
- Basé sur l’objet réel
Exemple d’utilisation
public class Main {
public static void main(String[] args) {
Animal chien = new Chien("Rex", 3, "Labrador");
Animal chat = new Chat("Mimi", 2, "Maincoon");
chien.faireDuBruit(); // Rex aboie
chat.faireDuBruit(); // Mimi miaule
chien.manger();
chat.dormir();
}
}Même si chien est déclaré comme Animal, c’est la version de Chien qui peut être exécutée si la méthode est redéfinie.
Dans notre exemple on parle de polymorphisme dynamique.
Pourquoi utiliser le polymorphisme ?
On utilise le polymorphisme pour améliorer la lisibilité du code en favorisant sa réutilisation et en éliminant les redondances.
Grâce au polymorphisme, on peut écrire du code générique qui opère sur des objets de types différents sans avoir besoin de connaître leurs implémentations spécifiques.
Sans polymorphisme :
if(animal instanceof Chien) {
((Chien) animal).faireDuBruit();
} else if(animal instanceof Chat) {
((Chat) animal).faireDuBruit();
}Ce code est fragile, et difficile à maintenir.
Avec polymorphisme :
animal.faireDuBruit();Il en résulte un code plus court, plus concis, plus facile à comprendre et à maintenir.