Sommaire
- La syntaxe
- Les blocs
- Les classes
- Héritage / classes abstraites / interfaces (vous êtes ici)
Temps de lecture estimé : 45min-1h
Héritage
En programmation orientée objet, on est souvent amenés à créer des classes liées entre elles, par une relation de parenté ou de caractéristique commune.
Exemple : On veut créer des classes pouvant décrire :
- une personne quelconque
- un client de l'entreprise
- un employé de l'entreprise
On pourrait tout à fait créer trois classes différentes, mais on pourrait aussi remarquer que ces trois notions partagent des informations. Elles possèdent toutes un nom, un prénom, une adresse e-mail, etc.
La programmation orientée objet (notée POO) nous permet alors de faire ceci :
- une classe
Personne
contenant les informations communes à tout le monde- une classe
Client
contenant les informations spécifiques aux clients - une classe
Employe
contenant les informations spécifiques aux employés
- une classe
où les classes Client
et Employe
héritent de la classe Personne
Sur un schéma UML, un tiret (-
) devant une ligne signifie que l'élément est privé (private
).
Sur un schéma UML, l'héritage est noté par un trait continu terminé par une flèche vide de la classe fille à la classe mère.
En Java, cela se traduit par l'utilisation du mot-clé extends
:
class A extends B
signifie que la classe A hérite de la classe B.
Avant de défiler plus bas, essayez de reproduire les classes du schéma ci-dessus en Java.
Voici le résultat :
public class Personne
{
String nom;
String prenom;
String email;
}
public class Client extends Personne
{
String nomEntreprise;
}
public class Employe extends Personne
{
double salaire;
}
On peut rajouter le constructeur ainsi que les getters de Personne
:
public class Personne
{
String nom;
String prenom;
String email;
public Personne(String nom, String prenom, String email)
{
this.nom = nom;
this.prenom = prenom;
this.email = email;
}
public String getNom()
{
return this.nom;
}
public String getPrenom()
{
return this.prenom;
}
public String getEmail()
{
return this.email;
}
}
Essayons maintenant de définir le constructeur de Client
:
public class Client extends Personne
{
String nomEntreprise;
public Client(String nomEntreprise)
{
this.nomEntreprise = nomEntreprise;
}
}
Si on essaie de compiler le code ci-dessus, on obtiendra une erreur. Et pour cause : la classe Personne
possède un constructeur, mais le constructeur de Client
n'appelle pas ce dernier. Or, pour créer un classe il faut appeler son constructeur. Il faut donc que le constructeur de Client
appelle aussi celui de la classe mère (Personne
).
On utilise pour cela la notation super
:
public class Client extends Personne
{
String nomEntreprise;
public Client(String nom, String prenom, String email, String nomEntreprise)
{
super(nom, prenom, email); // constructeur de la classe mère (Personne)
this.nomEntreprise = nomEntreprise;
}
}
Idem pour la classe Employe
:
public class Employe extends Personne
{
double salaire;
public Employe(String nom, String prenom, String email, double salaire)
{
super(nom, prenom, email); // constructeur de la classe mère (Personne)
this.salaire = salaire;
}
}
Le code final :
public class Personne
{
String nom;
String prenom;
String email;
public Personne(String nom, String prenom, String email)
{
this.nom = nom;
this.prenom = prenom;
this.email = email;
}
public String getNom()
{
return this.nom;
}
public String getPrenom()
{
return this.prenom;
}
public String getEmail()
{
return this.email;
}
}
public class Client extends Personne
{
String nomEntreprise;
public Client(String nom, String prenom, String email, String nomEntreprise)
{
super(nom, prenom, email); // constructeur de la classe mère (Personne)
this.nomEntreprise = nomEntreprise;
}
public String getNomEntreprise()
{
return this.nomEntreprise;
}
}
public class Employe extends Personne
{
double salaire;
public Employe(String nom, String prenom, String email, double salaire)
{
super(nom, prenom, email); // constructeur de la classe mère (Personne)
this.salaire = salaire;
}
public double getSalaire()
{
return this.salaire;
}
}
Classes abstraites
Parfois, il arrive qu'on veuille définir une classe mère (comme Personne
plus haut) mais qui ne forme qu'un concept abstrait et pas un objet complet à part entière.
Par exemple, on pourrait vouloir définir une classe FigureGeometrique
et des classes Carre
, Rond
, etc. Pour autant, "créer une figure géométrique" n'a pas vraiment de sens, car on doit pour cela connaître la nature de la figure (forme, taille, etc).
Ainsi, la classe FigureGeometrique
sera abstraite, c'est-à-dire qu'elle ne peut pas être utilisée seule, il faut obligatoirement utiliser une classe fille.
Sur un schéma UML, un plus (+
) devant une ligne signifie que l'élément est public (public
).
Sur un schéma UML, un élément (classe ou méthode) abstrait est noté en italique.
En Java, un élément abstrait est marqué abstract
.
public abstract class ClasseAbstraite
{
public String maFonctionPasAbstraite();
public abstract int maFonctionAbstraite();
}
correspond au schéma suivant :
Essayez de faire en Java les classes FigureGeometrique
, Carre
et Rond
(avec les constructeurs, les getters et les méthodes).
Correction :
public abstract class FigureGeometrique
{
public abstract double getSurface();
public abstract double getPerimetre();
}
public class Carre extends FigureGeometrique
{
double longueurCote;
public Carre(double cote)
{
this.longueurCote = cote;
}
public double getLongueurCote()
{
return this.longueurCote;
}
public double getSurface()
{
return this.longueurCote * this.longueurCote;
}
public double getPerimetre()
{
return this.longueurCote * 4;
}
}
public class Rond extends FigureGeometrique
{
double rayon;
public Rond(double rayon)
{
this.rayon = rayon;
}
public double getRayon()
{
return this.rayon;
}
public double getDiametre()
{
return 2 * this.rayon;
}
public double getSurface()
{
return Math.PI * this.rayon * this.rayon;
}
public double getPerimetre()
{
return 2 * Math.PI * this.rayon;
}
}
En Java, la classe Math fournit des fonctions et des constantes mathématiques. Math.PI
contient par exemple la valeur de π à 15 décimales.
Interfaces
Nous avons vu que l'héritage de classe marque un lien de parenté direct :
- un
Rond
est uneFigureGeometrique
, c'est sa nature profonde, il y a un lien parent-enfant entre les deux classes. - idem pour un
Client
et unePersonne
.
Les interfaces permettent au contraire de marquer la simple possession d'une caractéristique.
Exemple de cas de figure : vous voulez pouvoir comparer deux objets quelconques. Naturellement, il n'y a pas de moyen prédéfini pour le faire, tout simplement parce que tous les objets ne se comparent pas. Mais prenons le cas d'objets pouvant être comparés. Comment pourrions-nous formaliser ça dans le code ?
Nous avons les classes suivantes :
En Java, <T>
signifie que la classe ou l'interface est paramétrée (ou générique), ici par exemple l'interface Comparable
prend en paramètre un type, c'est le type avec lequel une classe peut se comparer.
Ici, la classe Rond
va implémenter l'interface Comparable<Rond>
, cela signifie qu'un rond ne peut être comparé qu'à un autre rond.
Là où l'héritage se note avec extends
, l'implémentation se note avec implements
.
public interface MonInterface
{
int maFonction();
}
public class MaClasse implements MonInterface
{
public int maFonction()
{
return 123;
}
}
correspond au schéma suivant :
Sur un schéma UML, l'implémentation est noté par un trait discontinu terminé par une flèche vide de la classe à l'interface.
En Java, quand on déclare une interface, les méthodes sont par défaut publiques, donc il n'est pas nécessaire de préciser public
devant chaque ligne.
Essayez d'écrire l'interface et les classes du schéma.
Pas la peine de réécrire toutes les fonctions des classes Employe
et Rond
, ne faites que les fonctions liées à l'interface.
Correction :
public interface Comparable<T>
{
boolean estPlusGrandQue(T autre);
}
public class Employe extends Personne implements Comparable<Employe>
{
...
public boolean estPlusGrandQue(Employe autre)
{
return this.salaire > autre.salaire;
}
...
}
public class Rond extends FigureGeometrique implements Comparable<Rond>
{
...
public boolean estPlusGrandQue(Rond autre)
{
return this.rayon > autre.rayon;
}
...
}
Pour plus d'informations sur l'UML, consultez l'article complet.
Merci à Kylian pour avoir signalé quelques erreurs