Cours Java
Cours Java
Introduction.
En quoi l’approche objet est-elle tellement attractive ?
La stabilité de la modélisation par rapport aux entités du monde réel, la construction itérative
facilitée par le couplage faible entre composants et la cohésion forte au sein d’un même
composant, la possibilité de réutiliser des éléments d’un développement à un autre. Certain
insistent également sur la simplicité du modèle qui fait appel seulement à cinq concepts
fondateurs (les objets, les messages, les classes, la généralisation et le polymorphisme) pour
exprimer de manière uniforme l’analyse, la conception et la réalisation d’une application
informatique.
D’une manière générale, toute méthode de construction de logiciels doit prendre en compte
l’organisation, la mise en relation et l’articulation de structures pour en faire émerger un
comportement macroscopique complexe : le système à réaliser. L’étude d’un système doit
prendre en considération l’agencement collectif des parties et conduire à une vue plus globale des
éléments qui le composent.
La construction d’un logiciel est par conséquent une suite d’itérations de genre « division
réunion » ; il faut décomposer, diviser pour comprendre et il faut composer, réunir pour
construire. Cela conduit à une situation paradoxale « il faut diviser pour réunir »
Face à ce paradoxe, le processus de décomposition a été dirigé traditionnellement par un critère
fonctionnel. Les fonctions du systèmes sont identifiées, puis décomposées en sous fonctions et
cela récursivement jusqu’à l’obtention d’éléments simples, directement représentables dans les
langage de programmation (par des fonctions et des procédures)
Fonction principale
L’architecture logicielle est alors le reflet des fonctions du système. Cette démarche apporte des
résultats satisfaisants lorsque les fonctions sont bien identifiées et lorsqu’elles sont stables dans le
temps. Toute fois étant données que la fonction induit une structure, les évolutions fonctionnelles
peuvent provoquer des modifications structurelles lourdes du fait du couplage statique entre
architecture et fonctions.
L’approche objet propose une méthode de décomposition, non basée uniquement sur ce que le
système fait, mais plutôt sur l’intégration de ce que le système est le fait. Initialement basée sur
l’utilisation d’objet en tant qu’abstraction du monde réel, l’approche objet a pour but une
modélisation des propriétés statiques et dynamiques de l’environnement dans lequel sont définis
les besoins. Les fonctions se représentent alors par des formes de collaboration entre les objets
qui composent le système. Le couplage devient dynamique, et les évolutions fonctionnelles ne
remettent plus en cause la structure statique du logiciel.
La force de l’objet provient de sa double capacité à décomposer « différencier » et recomposer
« réunir » du fait de la richesse de ses mécanismes d’intégrations, provient de sa capacité à
regrouper ce qui a été séparé, à construire le complexe à partir de l’élémentaire et à intégrer
statiquement et dynamiquement les constituants du système.
Notion d’objet
Dans les langage impératifs que vous connaissez, les déclarations des structures de données et les
déclarations des fonctions restent formellement séparées même quand les structures de données
sont associés à des groupes de fonctions qui les modifient.
Fonction1
Fonction3
Plutôt de décomposer un programme en deux parties, une qui déclare les structures de données et
une qui définit les fonctions associées, la programmation objet décompose le programme en
classes regroupant chaque structure de données avec les fonctions qui la modifient. Les instances
de ces classes sont appelées des objets. Un programme sera plus vu comme un programme
principal qui appelle un ensemble de fonctions, mais comme un ensemble de composants
autonomes qui interagissent entre eux pour faire émerger un résultat global.
Données
Conclusion.
La POO permet de créer des programmes modulaires dont le code est facilement réutilisable dans
d'autres applications (faible couplage + technique de l'héritage). Par ailleurs, elle assure l'intégrité
des données (par un mécanisme de masquage des membres de classes appelé encapsulation), ce
qui facilite la conception et la mise au point (déboggage).
L’approche objet
Un objet sans état ou sans comportement peut exister mais dans tous les cas il possède une
identité.
1.2.1. L’état.
L’état regroupe les valeurs instantanées de tous les attributs d’un objet (ses propriétés) sachant
qu’un attribut est une information qualifiant l’objet qui le contient. Chaque attribut peut prendre
une valeur dans un domaine de définition donnée. L’état d’un objet, à un instant donné,
correspond à une sélection de valeurs, parmi toutes les valeurs possibles des différents attributs.
Le diagramme suivants montre un objet voiture qui contient les valeurs de trois attributs
différents : la couleur, la masse et la puissance fiscale.
L’état évolue au cours du temps ; ainsi, lorsque la voiture roule, la quantité de carburant diminue,
certaines composantes de l’état peuvent êtres constantes : c’est le cas par exemple de la marque
de la voiture, ou encore du pays de sa construction.
1.2.2. Le comportement.
Le comportement regroupe toutes les compétences d’un objet et décrit les actions et les réactions
de cet objet, il est composé d’un ensemble d’opérations appelées méthodes. Les opérations d’un
objet sont déclenchées suite à une stimulation externe représentée sous forme d’un message
envoyé par un autre objet.
Le diagramme suivant, selon le valeur du message reçu, l’Opération 1 ou l’Opération 2 est
déclenchée.
Un autre objet
Un message
Opération 1 Opération 2
Un objet
L’état et le comportement sont liés ; en effet, le comportement à un instant donné dépend de l’état
courant, et l’état peut être modifié par le comportement. Il est possible de faire atterrir un avion à
la condition qu’il soit en train de voler ; en d’autres termes, le comportement Atterrir n’est valide
que si l’information En vol est valide. Après atterrissage, l’information En vol devient invalide, et
l’opération Atterrir n’a plus de sens. L’exemple suivant montre les liens entre l’état et le
comportement.
: Avion
[En vol]
Atterrir
Décoller :Avion
:Tour de contôle
[Au sol]
1.2.3. L’identité.
Tout objet possède une identité qui caractérise son existence propre. L’identité permet de
distinguer tout objet de façon non ambiguë, et cela indépendamment de son état. Ainsi il est
possible de distinguer deux objets dont toutes les valeurs d’attributs sont identiques.
Chaque objet possède une identité attribuée de manière implicite à la création de l’objet et jamais
modifié.
Routage
dynamique
Serveur1
Serveur3
Objet pavant
2. Les classes.
2.1 Définition
Une classe constitue un modèle de construction d’objet. Il s’agit d’une représentation abstraite
d’une entité concrète (ville, véhicule, étudiant) ou abstraite (date, réunion, planning de
réservation). La classe peut être considérée comme un moule à partir duquel on peut créer des
objets.
Une classe est un type de structure de données qui unit la liste des attributs d'un objet et les
méthodes qui opèrent sur ces attributs. Voici l'exemple classique de la classe Compte qui
représente un compte en banque qui possède 3 attributs (solde, numéro et propriétaire) et définit 3
méthodes (création, depot, retrait)
EN JAVA : EQUIVALENT EN C :
class Compte typedef struct
{ {
int solde; int solde;
int numero; int numero;
String proprietaire; char* proprietaire;
} Compte;
void creation(int num, String prop)
{ void creation(Compte *c, int num, char* prop)
solde = 0; {
numero = num; c->solde = 0;
proprietaire = prop; c->numero = num;
} c->proprietaire = prop;
}
void depot(int montant)
{ void depot(Compte* c, int montant)
solde = solde + montant; {
} c->solde = c->solde + montant;
}
void retrait(int montant)
{ void retrait(Compte* c, int montant)
solde = solde - montant; {
} c->solde = c->solde - montant;
} }
2.3. Instanciation.
Les objets informatiques sont construits à partir de la classe, par un processus appelé
instanciation. De ce fait l’objet est une instance d’une classe.
L’instance est une copie en mémoire de la classe pour laquelle les valeurs des attributs ont été
fixées et le code des méthodes ajouté au code de l'application ; c’est une représentation
« vivante ». d’une classe.
On peut alors déclarer des objets de type Compte. On dit que ces objets sont des instances de la
classe Compte. En Java, la déclaration d'un objet ne crée qu'une référence (pointeur) sur cet objet.
Il faut ensuite allouer cet objet en mémoire grâce à l'opérateur new.
EN JAVA : EQUIVALENT EN C :
Compte unCompte; Compte* unCompte;
unCompte = new Compte(); unCompte = (Compte*) alloc(sizeof(Compte));
Une fois l'objet créé, on peut accéder à ses attributs ou lui demander d'exécuter ses méthodes.
Pour ceci, on mentionne le nom de l'objet suivi d'un point suivi d'un nom d'attribut ou de méthode
et de ses paramètres :
EN JAVA: EQUIVALENT EN C:
unCompte.creation(1234, "Aymen"); creation(unCompte, 1234, "Aymen");
unCompte.depot(1000); depot(unCompte, 1000);
unCompte.retrait(450); retrait(unCompte, 450);
System.out.println("Il reste " + unCompte.solde + " printf("Il reste %f a %s", unCompte->solde,
a " + unCompte.proprietaire); unCompte->proprietaire);
Class Instance
solde = 1200
numero = 1234
Instanciation propriétaire = Aymen
solde = 700
numero = 1255
proprietaire = Imen
I. Les caractéristiques
Java possède un certain nombre de caractéristiques qui ont largement contribué à son énorme
succès.
Les applets fonctionnant sur le Web sous soumis aux restrictions suivantes dans la version 1.0 de
Java :
aucun programme ne peut ouvrir, lire, écrire ou effacer un fichier sur le système de
l'utilisateur
aucun programme ne peut lancer un autre programme sur le système de l'utilisateur
toute fenêtre créée par le programme est clairement identifiée comme étant une fenêtre
Java, ce qui interdit par exemple la création d'une fausse fenêtre demandant un mot de
passe
les programmes ne peuvent pas se connecter à d'autres sites Web que celui dont ils
proviennent.
Pour compiler un fichier source il suffit d'invoquer la commande javac avec le nom du fichier
source avec son extension .java
javac NomFichier.java
Le nom du fichier doit correspondre au nom de la classe principale en respectant la casse même si
le système d'exploitation n'y est pas sensible. Suite à la compilation, le pseudo code Java est
enregistré sous le nom NomFichier.class
Les arguments récupérés dans la fonction dans le tableau de chaînes de référence donnée
en argument (ici args) seront les arguments de la ligne de commande (les indices dans le
tableau commençant à 0), ainsi :
C:\> type Main.java
public class Main{
public static void main(String[ ] args){
for(int i = 0; i < args.length; i++)
System.out.println(args[i] + " ");
}
}
--> java Main aaa bbb ccc
aaa
bbb
ccc
C:\>
Le langage JAVA
1. Introduction
Nous traitons Java d'abord comme un langage de programmation classique. Nous aborderons les
objets ultérieurement.
Dans un programme on trouve deux choses
des données
les instructions qui les manipulent
Données
Instructions
1. La lecture du programme sera plus aisée si l'on a donné à la constante un nom significatif :
ex : final float taux_tva=0.18;
2. La modification du programme sera plus aisée si la "constante" vient à changer. Ainsi dans
le cas précédent, si le taux de tva passe à 33%, la seule modification à faire sera de modifier
l'instruction définissant sa valeur :
final float taux_tva=0.33;
Si l'on avait utilisé 0.18 explicitement dans le programme, ce serait alors de nombreuses
instructions qu'il faudrait modifier.
S=""+f;
affiche(S);
S=""+d;
affiche(S);
try{
d1=Float.valueOf("abcd").floatValue();
affiche(""+f1);
} catch (Exception e){
affiche("Erreur "+e);
}
} // fin main
} // fin classe
C'est ainsi que l'opération V1=V2=expression est légale. A cause de la priorité, c'est l'opérateur =
le plus à droite qui va être évalué.
On a donc V1=(V2=expression). L'expression V2=expression est évaluée et a pour valeur V.
L'évaluation de cette expression a provoqué l'affectation de V à V2. L'opérateur = suivant est
alors évalué sous la forme V1=V. La valeur de cette expression est encore V. Son évaluation
provoque l'affectation de V à V1. Ainsi donc, l'opération V1=V2=expression est une expression
dont l'évaluation provoque l'affectation de la valeur de expression aux variables V1 et V2 ; et
rend comme résultat la valeur de expression.
On peut généraliser à une expression du type : V1=V2=....=Vn=expression
ordre de priorité
>, >=, <, <=
==, !=
Le résultat d'une expression relationnelle est le booléen false si expression est fausse true sinon.
Exemple :
boolean fin;
int x;
fin=x>4;
2.5.6. L'opérateur ?
L'expression expr_cond ? expr1 : expr2 est évaluée de la façon suivante :
1. l'expression expr_cond est évaluée. C'est une expression conditionnelle à valeur vrai ou
faux
2. Si elle est vraie, la valeur de l'expression est celle de expr1. expr2 n'est pas évaluée.
3. Si elle est fausse, c'est l'inverse qui se produit : la valeur de l'expression est celle de expr2.
expr1 n'est pas évaluée.
Exemple.
Exemple :
int i, j;
float isurj;
isurj= (float)i/j; // priorité de () sur /
Ici il est nécessaire de changer le type de i ou j en réel sinon la division donnera le quotient entier
et non réel.
i est une valeur codée de façon exacte sur 2 octets
(float) i est la même valeur codée de façon approchée en réel sur 4 octets
Il y a donc transcodage de la valeur de i. Ce transcodage n'a lieu que le temps d'un calcul, la
variable i conservant toujours son type int.
L’entier n est le nombre de données que peut contenir le tableau. La syntaxe Tableau[i] désigne
la donnée n° i où i appartient à l'intervalle [0, n-1]. Toute référence à la donnée Tableau[i] où i
n'appartient pas à l'intervalle [0, n-1] provoquera une exception.
// tableau de 10 référence nulles vers 10 String (et non pas une chaîne de 10 caractères !)
String names [] = new String [10] ;
// tableau de 2 références vers des Points, créées et initialisées en même temps que le tableau.
Points hits[2] = new Point { { 10,20} , {11,21} } ;
// tableau de 3 références vers 3 String, créées et initialisées en même temps que le tableau
Tring riz [] = {« rond » , « long » , « parfumé » } ; // initialisé par les données
2.6.1 Accès à un élément
names.length attribut du tableau, fournit le nombre d’éléments alloués pour le tableau
hits[0].x fournit la valeur 10
hits[1].y fournit la valeur 21
Exemple :
public class test1{
public static void main(String arg[]){
float[][] taux=new float[2][2];
taux[1][0]=0.24F;
taux[1][1]=0.33F;
System.out.println(taux[1].length);
System.out.println(taux[1][1]);
}
}
et les résultats de l'exécution :
2
0.33
Cette méthode arrête le processus en cours et rend la valeur status au processus père, elle
provoque la fin du processus en cours et rend la main au processus appelant. La valeur de status
peut être utilisée par celui-ci. Sous DOS, cette variable status est rendue à DOS dans la variable
système ERRORLEVEL dont la valeur peut être testée dans un fichier batch. Sous Unix, c'est la
variable $? qui récupère la valeur de status si l'interpréteur de commandes est le Bourne Shell.
Exemple :
System.exit(0);
pour arrêter le programme avec une valeur d'état à 0.
Exemple
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
Notes
La valeur de l'expression de contrôle, ne peut être qu'un entier ou un caractère.
l'expression de contrôle est entourée de parenthèses.
la clause default peut être absente.
les valeurs vi sont des valeurs possibles de l'expression. Si l'expression a pour valeur vi ,
les actions derrière la clause case vi sont exécutées.
l'instruction break fait sortir de la structure de cas. Si elle est absente à la fin du bloc
d'instructions de la valeur vi, l'exécution se poursuit alors avec les instructions de la valeur
vi+1.
Exemple
En algorithmique
selon la valeur de choix
cas 0
arrêt
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
En Java
int choix, erreur;
switch switch(choix){
case 0: System.exit(0);
case 1: M1();break break;
case 2: M2();break break;
default default: erreur=1;
}
Notes
les 3 arguments du for sont à l'intérieur d'une parenthèse.
les 3 arguments du for sont séparés par des points-virgules.
chaque action du for est terminée par un point-virgule.
l'accolade n'est nécessaire que s'il y a plus d'une action.
l'accolade n'est pas suivie de point-virgule.
On boucle tant que la condition est vérifiée. La boucle peut ne jamais être exécutée.
Notes.
la condition est entourée de parenthèses.
chaque action est terminée par point-virgule.
l'accolade n'est nécessaire que s'il y a plus d'une action.
l'accolade n'est pas suivie de point-virgule.
do{
instructions;
}while (condition);
On boucle jusqu'à ce que la condition devienne fausse ou tant que la condition est vraie. Ici la
boucle est faite au moins une fois.
Note.
la condition est entourée de parenthèses.
On boucle tant que la condition est vraie (évaluée avant chaque tour de boucle).
Instructions_départ sont effectuées avant d'entrer dans la boucle pour la première fois.
Instructions_fin_boucle sont exécutées après chaque tour de boucle.
Notes.
les 3 arguments du for sont à l'intérieur de parenthèses.
les 3 arguments du for sont séparés par des points-virgules.
chaque action du for est terminée par un point-virgule.
l'accolade n'est nécessaire que s'il y a plus d'une action.
l'accolade n'est pas suivie de point-virgule.
les différentes instructions dans instructions_depart et instructions_fin_boucle sont
séparées par des virgules.
La structure algorithmique correspondante est la suivante :
instructions_départ
tantque condition
actions
instructions_fin_boucle
fintantque
Exemples
Les programmes suivants calculent tous la somme des n premiers nombres entiers.
1. for (i=1, somme=0 ; i<=n ; i=i+1)
somme = somme + a[i];
2. for (i=1, somme=0;i<=n;somme=somme+a[i], i=i+1);
3. i=1; somme=0;
while (i<=n)
{somme+=i; i++;}
4. i=1; somme=0;
do somme+=i++;
while (i<=n);
Le nom de l'argument arg peut être quelconque. C'est un tableau de chaînes de caractères
représentant les arguments de la ligne de commande. Nous y reviendrons un peu plus loin.
Au début du code source et avant la définition de la classe, il est usuel de trouver des instructions
d'importation de classes. Par exemple :
import java.io.*;
public class test1{
public static void main(String arg[]){
… code du programme
}// main
}
import java.io.*;
public class param1{
public static void main(String[] arg){
int i;
System.out.println("Nombre d'arguments="+arg.length);
for (i=0;i<arg.length;i++)
System.out.println("arg["+i+"]="+arg[i]);
}
}
Les instructions élémentaires apparaissent clairement lorsqu'on considère la structure d'un micro-
ordinateur et de ses périphériques.
Ecran
UC RAM
Disque
Clavier
Où expression est tout type de donnée qui puisse être converti en chaîne de caractères pour être
affiché à l'écran. Dans l'exemple précédent, nous avons vu deux instructions d'écriture :
System.out.println(taux[1].length);
System.out.println(taux[1][1]);
System.out écrit dans un fichier texte qui est par défaut l'écran. Il en est de même pour
System.err.
Ces fichiers portent un numéro (ou descripteur) respectivement 1 et 2. Le flux d'entrée du clavier
(System.in) est également considéré comme un fichier texte, de descripteur 0.
Java exige qu'elle soit gérée par le programmeur. Aussi, pour créer le flux d'entrée précédent, il
faudra en réalité écrire :
BufferedReader IN = null;
try {
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
System.err.println("Erreur " +e);
System.exit(1);
}
De nouveau, on ne cherchera pas à expliquer ici la gestion des exceptions. Une fois le flux IN
précédent construit, on peut lire une ligne de texte par l'instruction :
String ligne;
ligne=IN.readLine();
La ligne tapée au clavier est rangée dans la variable ligne et peut ensuite être exploitée par le
programme.
Héritage
Problématique
Une application a besoin de services dont une partie seulement est proposée par une classe déjà
définie (classe dont on ne possède pas nécessairement le code source), on veut utiliser ce code
sans le réécrire, c’est à dire définir une nouvelle classe à partir de la classe existante.
1. Définitions
L’héritage est l’un des concepts fondamentaux de la programmation par objets. Il
contribue à la modularité et la réutilisabilité. C’est un mécanisme qui permet à une entité
d’hériter des attributs et des comportements d’une autre entité.
L'héritage est une notion-clé de la programmation objet. Le mécanisme d'héritage consiste à créer
une nouvelle classe à partir d'une classe qui existe déjà en la complétant pour qu'elle permette de
créer des objets plus spécifiques. Si la classe B hérite (ou dérive) de A alors on dira que A est la
superclasse ou la classe-mère de B, tandis que B est la sous-classe ou la classe-fille ou dérivée de A
Quand B dérive de A, B possède implicitement tous les attributs et toutes les méthodes de A.
Dans la déclaration de B, on ajoute des attributs et des méthodes supplémentaires ou de
remplacements. Faire dériver une classe B à partir d'une classe A n'a de sens que si toutes les
instances possibles de B forment un sous-ensemble de l'ensemble de toutes les instances
possibles de A.
Par exemple un étudiant est une personne. Nous pouvons donc définir l’entité Etudiant en
réutilisant l’entité Personne. Il suffit simplement de compléter, d’une part les attributs de
l’entité personne par une filière d’inscription, une année dans cette filière et une moyenne,
d’autre part, les comportements de la personne pour enregistrer une moyenne, mais aussi
compléter les comportements saisir, afficher …
Schématiquement un héritage se décrit comme sur la figure 1. Sur cette figure, il n’existe pas de
comportement de comparaison propre à l’entité Etudiant. Pour comparer l’étudiant à un autre
étudiant, nous utiliserons le comportement comparerA de l’entité Personne. Etant donné que
l’étudiant hérite de l’entité Personne, il hérite de tout son comportement et donc de la
comparaison sans qu’il soit besoin de la redéfinir dans l’entité Etudiant. Ainsi, en langage java,
l’instruction
E.comparerA(NOM_PRENOM)
fait appel au comportement de E défini dans la classe Personne, pour comparer
alphabétiquement les identités des deux étudiants. ‘NOM_PRENOM’
En langage Java, pour dire qu’une classe hérite d’une autre classe, on utilise le mot réservé
« extends »
Personne
Attributs
nom
prénom
age
adresse
Méthodes
saisir
afficher
comparerA
Etudiant
Attributs
filière
année
moyenne
Méthodes
saisir
afficher
setMoyenne
getMoyenne
En langage Java, toutes les classes héritent d’une autre classe qui, par défaut est une classe
prédéfinie nommée ‘Object’. Cette classe prédéfinie n’hérite d’aucune autre classe ; elle est en
haut de la hiérarchie des classes. L’héritage de la classe Object n’a pas besoin d’être spécifié, il
est automatique.
La classe Etudiant est une sous classe de la classe Personne. La classe Personne est la classe
mère ou super classe de la classe Etudiant. La classe Personne est une sous classe de la classe
Object.
Exemple :
public classe Etudiant extends Personne {
public String filiere;
public String annee;
private float moyenne = -1;
} // fin de la classe
Véhicule-Terrerstre Véhicule-Marin
3.2. Généralisation
De même qu’il est possible de spécialiser des classes à partir d’autres classes, il est possible de
généraliser une classe à partir d’un ensemble de classes.
La généralisation est l’opération inverse de la spécialisation. Elle consiste à créer une classe qui
contient les traits communs à plusieurs classes.
Homme Singe
Exemple : Généraliser les deux classes
age poids
suivantes à l’aide d’une classe Primate: poids couleurPoils
numeroSS
couleurDesYeux
sauter( )
courrir( ) crier( )
sauter( ) aimer( )
se-mettre-en-colère( ) se-mettre-en-colère( )
aimer( )
écrire( )
4. La construction
Du point de vue sémantique, par rapport à la mémoire centrale, qu’est il sensé se passer quand
une classe A hérite d’une classe B et qu’un objet de la classe A est créé ?
Rappelons qu’en java, tout objet hérite d’une classe sauf la classe Object, et que ce sont les
instructions ‘new <Constructeur> ’ qui sont chargées de créer les instances des classes. Pour
les instances de la classe Object la sémantique opérationnelle de l’instruction new vue au
chapitre précédent reste valable mais pas pour les autres classes. La généralisation suivante
permet de prendre en compte la notion d’héritage.
Nous pouvons considérer que l’instruction ‘new <Constructeur> ’ de la classe A est chargée
de :
Créer une instance de la classe
Exécuter l’algorithme <Constructeur> de la classe A
Retourner un pointeur (adresse, type de l’objet) pointant vers l’espace réservé pour
cet objet.
Créer une instance d’une classe A qui hérite d’une classe B consiste en :
Créer une instance de la super-classe B. (cette action est omise dans le cas où A est la
classe Object).
A l’intérieur de l’espace de l’objet qui vient d’être créé, réserver l’espace nécessaire pour
un objet de type A.
A l’intérieur de ce nouvel espace, réserver la place nécessaire à chaque attribut de l’objet
de type A. Ces espaces contiendront des valeurs inconnues sauf s’ils sont explicitement
initialisés, auquel cas, ils contiendront les valeurs d’initialisation.
public A() {
super();
public class A extends Object { }
// attributs
String nom; Constructeur par défaut implicite
// méthodes
String getNom() {
return nom;
}
...
}
Cours Programmation Orientée Objet 36/58
ISET-Sousse
Remarque.
Si on le constructeur par défaut est masqué dans une classe mère, il faut définir
explicitement le constructeur par défaut aux classe dérivées
Pour construire une instance d’une classe A donnée, il faut construire auparavant une instance de
la classe mère de A. Appelons B la classe mère de A. Par suite, l’instance de la classe mère doit
être initialisée avant l’instance de la classe A qu’elle contient. Ainsi, au moment de l’initialisation
de l’objet de type A, les attributs de l’objet de type B dont il hérite sont initialisés et peuvent être
utilisés pour les attributs spécifiques à l’objet de type A. Pour réaliser cela, il existe quelques
conventions résumées ci-dessous :
Convention 1
Si une classe A ne possède pas de constructeur explicitement défini, elle est affectée d’un
constructeur vide par défaut :
public A() {}
Convention 2
Toute classe qui possède au moins un constructeur explicitement défini, ne possède pas de
constructeur par défaut.
Convention 3
Excepté pour la classe Object, la première instruction d’un constructeur est l’appel d’un
constructeur de la classe mère ou d’un autre constructeur de la classe courante. Si cette
instruction ne figure pas explicitement dans le constructeur, un appel au constructeur sans
argument de la classe mère est ajouté automatiquement par le compilateur.
Ces conventions assurent que pour chaque objet emboîté, un constructeur est exécuté.
4.3. Exemple
Reprenons notre exemple de la classe Etudiant. L’instruction déclaration suivante
Etudiant E = new Etudiant() ;
après avoir créé le doublet E, va lancer l’exécution ‘new Etudiant()’. L’instruction new va
donc créer une instance de la classe Object, puis, à l’intérieur de celle-ci une instance de la
classe Personne, et à l’intérieur de cette dernière, une instance de la classe Etudiant. Cette
situation est illustrée figure 2.
Un étudiant est une personne particulière. Il s’agit donc d’une personne ayant des attributs et des
comportements supplémentaires. C’est ce qui explique que les attributs supplémentaires et donc
toutes les particularités de l’Etudiant soient à l’intérieur de la Personne. On remarquera que, dans
notre définition de la classe Etudiant, le champ moyenne des attributs spécifiques à l’étudiant est
initialisé. Ceci explique que sur le schéma 2 ce champ porte une valeur spécifique, donnée par le
programmeur alors que les autres champs ont une valeur par défaut, attribuée par Java à la
création de l’objet.
Dans la mesure où nous n’avons pas défini de constructeur pour la classe Etudiant, elle possède
un constructeur par défaut :
public Etudiant() {} ; (cf. convention 1 ci-dessus).
Ce constructeur par défaut doit appeler en première instruction un constructeur. D’après la
convention 3, il s’appellera Personne(). Ce constructeur que nous avons déjà défini
précédemment ne fait rien hormis appeler le constructeur de la classe Object.
Supposons que dans la classe Etudiant nous ayons le constructeur :
public Etudiant(String nom, String prenom, int age,
String filiere, String annee) {
Personne(nom, prenom, age);
this.filiere = filiere ;
this.annee = annee ;
}
public Personne (String nom, String prenom, int age, String adresse) {
Personne(nom, prenom, age);
this.adresse = adresse ;
}
Pour les appels d’un constructeur dans un autre constructeur, il est possible d’utiliser les mots clé
this et super à la place des noms explicites de constructeurs. Ce qui donne, dans les deux cas
précédents les écritures équivalentes :
Public Personne (String nom, String prenom, int age, String adresse) {
this(nom, prenom, age);
this.adresse = adresse ;
}
En ce qui concerne la visibilité, nous avons les mêmes règles que pour les espaces emboîtés. Ceci
est valable tant pour les attributs que pour les méthodes, comme nous l’avons vu dans la section
2.5 du chapitre précédent. Toutefois l’appel d’une méthode dans une autre méthode peut poser
d’autres problèmes que nous étudierons dans la section 7 sur la redéfinition. Ainsi, si E est un
étudiant, l’appel à la méthode setMoyenne par E.setMoyenne créera un espace pour
l’algorithme correspondant à setMoyenne, à l’intérieur de l’espace de l’étudiant E cf. figure 5.
Les règles de visibilité font que de l’intérieur de l’algorithme setMoyenne, l’attribut moyenne
de l’étudiant E n’est pas vu. D’ou la nécessité de le désigner explicitement comme attribut de
l’objet par this.moyenne, ce qui est fait dans l’algorithme donné page Erreur ! Signet non
défini.. En revanche il n’y a pas de variable d’entrée ni intermédiaire dans l’algorithme
getMoyenne défini page Erreur ! Signet non défini., si bien que this n’est pas nécessaire.
C’est donc la valeur de l’attribut moyenne de E qui sera prise comme valeur de l’expression à
rendre.
5. Transtypage
exemple :
...
// on peut écrire
m=rex;
// ici le type réel de m devient Chien mais ça ne pose aucun problème
// car un chien a tous les attributs et méthodes de Mammifère.
rex = m;
// ici, on a un problème, le mammifère ne dispose pas des
// nouveaux attributs du chien.
rex = (chien) m;
// cette commande sera acceptée à la compilation mais elle
// peut poser des problèmes à l'exécution si le type réel de
// m n'est pas chien ( ou une classe dérivée de chien.
// en fait, on force la compilation mais on ne converti pas m.
Règle 1 : mère = fille toujours OK( il fait que le type réel d'un objet peut être différent du type
déclaré.
Règle 2 : fille = (fille) mère OK mais peut planter à l'exécution donc il faut éviter ou s'assurer des
conséquences.
Règle 3 : Classe1=Classe2 jamais ok si il n'y a aucune relation de parenté entre les deux classes.
On peut aussi utiliser le transtypage pour mettre des objets de classes différentes dans un même
tableau.
Regle 4 : un objet peut toujours être traité comme s'il était du type de celui de sa classe mère.
L'inverse est faux.
6. Masquage et redéfinition
En programmation objet, il est possible pour un attribut d’une classe dérivée de porter le même
nom qu’un attribut d’une classe ancêtre. De même, il est possible pour une méthode d’une classe
dérivée de porter la même signature (même nom, et même suite des types de ses paramètres)
qu’une méthode d’une classe ancêtre. Dans les deux cas, cette possibilité est assortie de
conditions et va suivre quelques règles que nous allons étudier dans les sections suivantes. (Par
exemple si un attribut ou une méthode est finale, nous n’avons pas cette possibilité).
Ces règles vont se répartir entre deux phénomènes : le masquage qui concerne les attributs et les
méthodes et la redéfinition qui concerne exclusivement les méthodes. De manière simplifiée :
Le masquage est la faculté de cacher un attribut ou une méthode d’une classe ancêtre qui,
sans cela, serait visible.
La redéfinition est la faculté, dans un objet donné, d’utiliser une méthode définie dans une
classe dérivée, à partir d’une méthode d’une classe ancêtre.
Si l’on se place au niveau des espaces emboîtés, le masquage s’intéresse à ce qui se passe en
sortant des espaces vers des espaces englobants, alors que la redéfinition donne la possibilité
d’aller vers des espaces englobés.
Notations : de l’intérieur d’une classe, il est possible d’atteindre un attribut ou une méthode de la
classe mère dont elle hérite directement, en utilisant le mot clé super. Par exemple : super.a ou
super.afficher(String, …). Bien entendu, ceci n’a de sens que si, d’une part, il y a ambiguïté
(deux attributs de même nom ou deux méthodes de même signature), et si, d’autre part, les règles
de visibilité sont respectées. Par ailleurs, la notation super.super n’est pas autorisée.
Remarque : il ne faut pas confondre la possibilité d’avoir des méthodes ayant la même signature
dans une classe et une classe dérivée, avec la surcharge ou le polymorphisme qui est la possibilité
d’avoir, dans une même classe, des méthodes de même nom mais de signatures distinctes.
6.1. Le masquage
Nous ne parlerons, dans cette section, que des attributs. Nous traiterons globalement les
comportements (les méthodes) dans la section suivante (section 6.2) car les méthodes se
comportent en général différemment des attributs.
Lorsque, dans une méthode nous rencontrons une variable ou un attribut non défini dans cette
méthode, où aller chercher l’espace correspondant ? C’est la sémantique des espaces emboîtés
qui permet de le déterminer. Comme nous l’avons vu, la recherche se fait dans l’ordre suivant
Puis la recherche dans l’instance se fait de manière récursive dans l’ordre suivant :
1. Dans la partie non statique de l’instance
2. Dans sa partie statique
3. Dans l’espace de sa super-instance.
Toutefois la recherche peut être infructueuse si, en remontant ainsi, le premier attribut rencontré
et portant le nom recherché est qualifié par un modificateur le rendant invisible à un objet d’une
classe dérivée d’où est partie la recherche.
Attention : il ne peut y avoir deux attributs portant le même nom dans une même classe. En
particulier, il ne peut y avoir d’attributs portant le même nom à la fois dans la partie statique et
dans la partie non statique d’une classe. En revanche, une variable locale à une méthode peut
porter le même nom qu’un attribut de la classe dans laquelle elle est définie.
Puisqu’il est possible d’avoir des attributs portant le même nom dans une classe et dans ses sous-
classes, lorsqu’on est dans l’une des sous-classes ou encore plus bas dans une classe dérivée, on
dit que l’attribut de la sous-classe cache ou masque l’attribut de la classe mère, lorsqu’on se
trouve dans la sous-classe ou dans une de ses classes dérivées.
Il n’y a pas de contraintes particulières sur les modificateurs pour le masquage d’attributs excepté
qu’un attribut final ne peut être masqué. Si un attribut est déclaré final, aucune de ses sous-
classes, à quelque niveau que ce soit, ne peut avoir d’attributs portant son nom.
Par exemple, un attribut privé et/ou non statique peut masquer un attribut public et/ou statique et
inversement.
Du point de vue notation nous pouvons distinguer quatre cas :
Attribut seul : appliquer la règle de recherche donnée ci-dessus, à partir du lieu où l’attribut est
rencontré,
Attribut attaché à un objet (x.a, this.a ou super.a) : appliquer la règle de recherche à partir de
l’espace de l’objet désigné, correspondant au type de cet objet.
Attribut attaché à un objet transtypé : appliquer la règle de recherche à partir de l’espace de
l’objet désigné, correspondant au nouveau type.
Attribut attaché à une classe : si l’attribut n’existe pas dans la classe, appliquer la règle de
recherche à partir de cette classe. Si un attribut non statique masque l’attribut statique, une erreur
de compilation est alors émise. Toutefois, c’est une idée irrationnelle d’attacher un attribut
statique à une classe dérivée et non à la classe dans laquelle il a été défini.
Exemple : soient une classe A, B une sous classe de A, et C une sous classe de B. Chacune de ces
classes contient un attribut public entier a, initialisé respectivement à 1 dans A, à 2 dans B et à 3
dans C. Alors,
C z = new C(); // z est un objet de type C
B y = new B(); // y est un objet de type B
A x = new A(); // x est un objet de type A
z.a vaut 3
y.a vaut 2
x.a vaut 1
(B)z.a vaut 2
(A)z.a vaut 1
(A)y.a vaut 1
(C)y.a vaut 3
(C)x.a vaut 3
Si on est dans l’objet z, dans l’espace dérivé le plus bas, (B)this.a vaut 2 et (A)this.a vaut 1.
6.2. La redéfinition
6.2.1. Généralités
Dans cette section, nous abordons le cas des méthodes portant la même signature dans une classe
et une classe dérivée. Pour que cela soit possible, ces méthodes doivent suivre quelques règles :
Classe ancêtre
Classe dérivée public protected <aucun> private
Public oui oui oui oui
Protected non oui oui oui
<aucun> non non oui oui
private non non non oui
La règle 1 ne concerne que la visibilité, et donc s’applique tant aux méthodes de classe (statiques)
qu’aux méthodes d’instance (non statiques).
class A {
public void f (int n) {…}
}
class B extends A {
Règle 4 : dans une classe, il ne peut y avoir de méthode ayant une signature identique à une
méthode finale visible appartenant à l’une de ses classes ancêtres.
6.2.2. Redéfinition
Exemple :
définissons dans la classe Personne la méthode :
public void afficher(String finDeLigne)
{
System.out.print(nom+" " + prenom);
System.out.print(" (" + age +" ans )");
System.out.print (" ; " + adresse);
System.out.print (finDeLigne);
}
System.out.print (finDeLigne);
}
Supposons que le tableau de Personne t contienne 3 personnes (taille vaut 3) et que la troisième
personne soit un étudiant. Un étudiant étant une personne, elle sera acceptée en tant que telle dans
le tableau (transtypage naturel).
Ce qui est remarquable dans cet exemple, est que, pour la troisième personne, la méthode utilisée
est celle de l’étudiant, alors que personne est un tableau de Personne. Si, à la place de la méthode
afficher, nous avions fait appel à un attribut a, et que cet attribut soit défini dans les deux classes
Personne et Etudiant, alors seul l’attribut de la classe Personne aurait été utilisé, que l’élément
stocké dans le tableau soit une personne ou un étudiant (voir masquage). Tandis qu’avec les
méthodes, c’est la méthode de la classe la plus dérivée qui est utilisée.
Ceci a un intérêt car si l’on cherche les caractéristiques d’une personne, toutes ses
caractéristiques connues sont affichées. Si ce n’est qu’une personne sans autre connaissances,
alors seules les caractéristiques de la personne seront affichées. Mais si nous en connaissons plus,
par exemple pour l’étudiant, alors nous aurons un affichage plus important. Ce phénomène
s’appelle la redéfinition. C’est une caractéristique importante de la technologie objet. Les
attributs ne sont pas redéfinis. Deux attributs peuvent porter le même nom dans des classes
distinctes d’une même filiation, mais on ne parle pas de redéfinition. On parle seulement de
masquage. En revanche, pour les méthodes, nous avons cette nouvelle notion de redéfinition.
Toutefois, la redéfinition ne s’applique pas à toutes les méthodes.
Règle 5 : les méthodes statiques, les méthodes privées et les méthodes finales suivent les règles
du masquage et non celles de la redéfinition.
Exemple : soient une classe A, une classe B qui hérite de A, et une classe C qui hérite de B.
Supposons que A, B, C contiennent toutes les trois une méthode publique non statique m(). Alors
la méthode m() de C redéfinit la méthode m() de B, qui elle même redéfinit la méthode m() de A.
La méthode m() de A contient l’unique instruction :
Les méthodes m() de B et C sont identiques au nom de la classe près. Supposons de plus que dans
la classe A et uniquement dans A soit définie la méthode f() suivante, qui fait un simple appel à la
méthode m() ;
public void f() {
m() ;
}
soient x un objet de type A, y un objet de type B et z un objet de type C. Alors :
l’instruction x.f() affichera : Je suis dans A
l’instruction y.f()affichera : Je suis dans B
l’instruction z.f()affichera : Je suis dans C
Il faut remarquer, dans le dernier cas, que la méthode f() ne figurant pas dans la classe C, est
héritée de la classe A et que depuis l’espace correspondant, elle revient chercher la méthode m()
de la classe C.
Un appel classique de méthode non statique f(..) passe par un objet u de type X. Cet objet bien
qu’instance de la classe X, peut résulter du transtypage d’une classe Y dérivée de X. Or nous
avons vu que l’adresse de u est celle de Y : u contient la valeur (adresse de Y, X). La recherche
dynamique va se faire prioritairement entre l’espace du niveau de Y et l’espace du niveau de X.
Dans l’exemple ci-dessus, illustré figure 15, si t est une instance de la classe A initialisée par t =
z, l’instruction t.m() cherchera m() dans l’espace de niveau C, puis de niveau B puis de niveau A.
La recherche suivra alors l’algorithme suivant, dans lequel, en entrée nous avons
MethodeArecherche la signature de la méthode à rechercher
NiveauMini désigne l’espace de niveau minimal dans lequel est recherchée la méthode
(ici celui de Y). C’est toujours le plus interne de la zone de recherche.
NiveauMaxi désigne l’espace de niveau maximal dans lequel est recherchée la méthode. Il
s’agit de l’espace du niveau de la classe réelle de l’objet (ici celui de X). C’est toujours le
plus externe de la zone de recherche. Ce niveau sera exclu de la recherche ci-dessous.
Variables intermédiaires sont
niveauCourant désigne l’espace dans lequel est recherchée la méthode. Au début de
l’algorithme, il s’agit de l’espace du niveau de la classe réelle de l’objet (ici celui de Y).
C’est toujours le plus interne de la zone de recherche.
NiveauMéthodeAAppliquer est le niveau de la méthode à appliquer. Au début de
l’algorithme elle est initialisée à vide (0)
Modificateurs est un doublet contenant la liste des modificateurs de la méthode à
rechercher dans un niveau courant précédent et ce niveau précédent. Au début de
l’algorithme elle est initialisée à vide (0,0).
Nous dirons que des modificateurs M1 d’une méthode sont prioritaires sur des modificateurs M2
d’une méthode de même signature dans une classe dérivée, soit si M1 contient le modificateur
static, soit si M1 rend non visible la méthode modifiée par M1 à partir du niveau de M2.
Debut :
niveauCourant niveauMini
niveauMethodeAAppliquer 0
modificateurs 0
tant que (niveauCourant <= niveau Maxi) faire
niveauCourant recherche le niveau de la méthodeARecherche par
la règle des espaces emboîtés à partir du niveauCourant
sans tenir compte de la visibilité (retourne 0 si non trouvée).
Si (niveauCourant est vide) ou si (niveauMaxi < niveauCourant
et methodeArechercher du niveauCourant est non visible
depuis niveauMaxi),
Alors la méthode n’existe pas dans l’espace niveau maximum
ni au dessus. Rendre (0,0). Il n’y a pas de méthode dont
la signature corresponde à la méthode recherchée
Sinon si (niveauMethodeAAppliquer = 0)
ou si (modificateurs prioritaires sur les modificateurs
de methodeARecherche du niveauCourant)
NiveauMethodeAAppliquer niveauCourant
Fin des si
niveauCourant niveau immediatement superieur à niveauCourant
modificateur (modificateurs de niveauCourant, niveauCourant)
fin tant que
rendre niveauMethodeAAppliquer.
fin
Remarquons que cet algorithme permet de faire se succéder la notion de redéfinition (recherche
vers les espaces englobés) et celle du masquage (recherche vers les espaces englobants) si la
première est infructueuse. En particulier, pour les méthodes statiques, privées ou finales du
niveau maximum, tout se passe comme s’il n’y avait que la notion de masquage.
Ecrivons par exemple, une méthode saisir pour la classe Etudiant
Titre de l’algorithme : saisir
Entrées : un flot d’entrée pour prendre les informations. Nous utiliserons pour simplifier une
instance de la classe Ask fournie dans la bibliothèque utilitaire. Nous nommerons l’objet
correspondant : demande.
Finalité : remplir les attributs de l’objet. (modifier son état).
Sortie : un booléen vrai si la saisie s’est terminée correctement, faux sinon.
Stratégie : nous utiliserons, tout d’abord le comportement saisir de la classe Personne, pour
récupérer les valeurs des attributs de l’étudiant en tant que Personne. Puis, si la saisie s’est bien
passée jusque là, nous saisirons les valeurs des attributs filiere et année, en utilisant les méthodes
de l’objet demande. Les chaînes de caractères seront traduites en majuscules par la méthode
toUpperCase de la classe String.
La moyenne n’est pas saisie. Elle le sera au moment des examens, par la méthode setMoyenne.
Variables intermédiaires : Aucune
Méthodes utilisées : les deux méthodes de l’objet demande utilisées, renvoient les exceptions
suivantes : IOException si le flot d’entrée n’est pas correct, et NullPointerException en cas
d’erreur de saisie due à la terminaison du flot d’entrée avant la fin de la saisie. Cette dernière
erreur survient, par exemple, lorsque la saisie se fait à partir d’un fichier qui ne contient pas assez
de données (la fin du fichier est atteinte avant la fin de la saisie).
ChaineCompacte(String commentaire, int tailleMax) de la classe Ask.. Elle est
chargée de récupérer sur le flot d’entrée correspondant à l’objet de demande courant, une
chaîne de caractères, d’y supprimer les blancs commençants et finissants, et de réduire à
un seul espace les suites de blancs intermédiaires. La chaîne finale aura au maximum
tailleMax caractères. La saisie de la chaîne finale aura au maximum tailleMax
caractères. Cette méthode rend un pointeur vers l’objet de type String résultant.
Entier(String commentaire, int max, int min) de la classe Ask. Elle est chargée
de récupérerr sur le flot d’entrée correspondant à l’objet de demande courant, un entier de
type int compris entre les valeurs min et max incluses. La saisie de l’entier se fera après
affichage du message si le flot d’entrée est le flot standard. Cette méthode rend l’entier
saisi.
toUpperCase() de la classe String. Cette méthode crée un nouvel objet de
type String représentant une suite de caractères identique en longueur à la
chaîne en cours de conversion, et dont chaque caractère est la traduction
en majuscule du caractère correspondant dans la chaîne en cours de
conversion
saisir(demande) de la classe Personne. Cette méthode est chargée de
récupérer les valeurs des attributs d’une Personne sur le flot d’entrée
associé à l’objet demande. Elle rend le booléen false si le nom de la
personne est null, true si non.
7. La classe Final :
une classe final est une classe générale. On ne peut JAMAIS hériter d'une classe final.
class Generale {
final void methode_generale(){...} // ici, on peut hériter de la classe Générale mais on ne peut pas
redéfinir la méthode methode_generale().
Approfondissement
Voici un exemple complet montrant ce qui se passe exactement dans une situation délicate : des
appels de méthodes static dans des méthodes héritées...
class A {
System.out.println("methode a");
b();
class B extends A {
B b = new B();
b.a(); // ici, la méthode b() appelée sera celle de la classe A
Encapsulation
A. Paquetage.
Un paquetage [package] est le regroupement sous un nom d'un ensemble de classes. Les
paquetages de Java sont hiérarchisés sous la forme d'une arborescence. En chaque noeud de
l'arborescence peut se trouver un paquetage.
Un paquetage se désigne par un ensemble d'identifiants séparés par des points. Ex :
java.awt.geom est un paquetage de Java contenant des classes définissant des objets
géométriques. A l'arborescence logique des paquetages correspond l'arborescence physique des
répertoires et fichiers mémorisants les fichiers *.class des paquetages. Ex : les classes du
paquetage java.awt.geom sont stockées dans le répertoire java\awt\geom à partir du répertoire
racine des paquetages.
1. Attribution d’une classe à un paquetage
Un paquetage est caractérisé par un nom qui est soit un simple identificateur, soit une suite
d’identificateurs séparés par des points, comme dans :
mesClasses
utilitaires.mathematiques
utilitaires.tris
Pour inclure les classes d'un fichier source dans un paquetage, il faut placer la commande
package monPaquetage; en tête du fichier. Les fichiers *.java créés seront stockés dans le
répertoire MonPaquetage.
Cette instruction est suffisante, même lorsque le fichier concerné est le premier auquel on attribue
le nom de paquetage en question. En effet, la notion de paquetage est une notion "logique",
n’ayant qu’un rapport partiel avec la localisation effective des classes ou des fichiers au sein de
répertoires
De même, lorsqu’on recourt à des noms de paquetages hiérarchisés (comme Utilitaires.Tris), il
ne s’agit toujours que d’une facilité d’organisation logique des noms de paquetage. En effet, on
ne pourra jamais désigner simultanément deux paquetages tels que
Utilitaires.Mathematiques et Utilitaires.Tris en se contentant de citer Utilitaires. Qui plus
est, ce dernier pourra très bien correspondre à d’autres classes, sans rapport avec les précédentes.
Pour utiliser une ou plusieurs classes définies dans un paquetage, il y a plusieurs solutions :
a. En citant le nom de la classe. Expliciter le paquetage de la classe en préfixant le nom
de la classe par son nom de paquetage : monPaquetage.A a = new A();
b. En important une classe. Utiliser en tête du fichier l'instruction import suivi du nom du
paquetage et de la classe : import monPaquetage.A; On n'a alors plus besoin de préfixer
le nom de la classe dans le reste du fichier : A a = new A();
c. En important un paquetage. Importer toutes classes d'un paquetage : import
monPaquetage.*; On n'a alors plus besoin de préfixer aucun nom de classe du
paquetage dans le reste du fichier.
B. Encapsulation.
L'encapsulation est une notion importante de la programmation objet et du génie logiciel. Elle
consiste à masquer le plus possible les détails d'implémentation et le fonctionnement interne des
objets. Cette dissimulation permet de découpler les objets constituants un programme afin que la
modification de la structure interne d'une classe ne remet pas en cause le code utilisant celle-ci.
L'encapsulation permet aussi la réutilisation d'une classe dans un autre contexte (cas des
bibliothèques).
données propres à un objet ne sont accessibles qu'au travers des méthodes de cet objet sécurité
des données : elles ne sont accessibles qu'au travers de méthodes en lesquelles on peut avoir
confiance masquer l'implémentation : l'implémentation d'une classe peut être modifiée sans
remettre en cause le code utilisant celle-ci
B.1. Niveaux d'accès
En Java, on peut restreindre l'accès à des classes, des méthodes et des attributs. Cette restriction
les rend invisibles et inréférençables en dehors de leurs niveaux d'accès. Concernant les membres
(méthodes et attributs) des classes, il existe 4 niveaux d'accès (visibilité):
public accessible à toute autre classe, le membre est accessible de n'importe où.
protected est accessible dans la classe où il est défini, dans toutes ses sous-classes et dans
toutes les classes du même package
package (visibilité par défaut) le membre n'est accessible que dans les classes du même
package que celui de la classe où il est défini
private n'est accessible qu'à l'intérieur de la classe où il est défini
Concernant les classes, il y a deux niveaux d'accès : public ou privé paquetage. Dans un même
fichier, une seule classe au maximum peut être déclarée publique. Si une classe contient la
méthode statique main alors c'est cette classe qui doit être publique.
Les 4 premières lisent et affectent directement les attributs partieReelle et partieImaginaire tandis
que les 4 dernières le font indirectement via des calculs (mais l'utilisateur de la classe n'est pas
censé le savoir). Plus tard on peut décider de remplacer les attributs partieReelle et
partieImaginaire par rho et theta ou d'ajouter ces deux derniers. Il faudra alors modifier (une
partie) de ces 8 méthodes mais il n'y aura pas besoin de modifier le code des classes utilisant la
classe Complexe : la modification de la structure interne de cette classe est transparente pour les
classes qui l'utilisent.
B.2.2. Sécurité des données : elles ne sont accessibles qu'au travers de méthodes en lesquelles
on peut avoir confiance
A titre d’exemple, il est conseillé de déclarer moyenne dans la classe Etudiant avec le
modificateur private. En effet c’est un attribut sensible, pour lequel des vérifications doivent
être faites avant de prendre en compte sa valeur. Par exemple, la moyenne doit être comprise
entre 0 et 20. Si la moyenne n’est pas attribuée, alors, par convention, la valeur de la moyenne est
–1. Parfois, pour des problèmes de sécurité, seules quelques personnes ont l’autorisation de
modifier la moyenne … En protégeant ainsi l’attribut moyenne par le modificateur private, on
se préserve de modifications inappropriées ou non autorisées. On appelle cette technique de
protection : l’encapsulation. L’encapsulation impose, dans ce cas, de pouvoir accéder à la
moyenne par un autre moyen. Il faut donc au moins deux méthodes pour cela : la méthode
setMoyenne pour modifier la valeur de la moyenne de manière appropriée, et la méthode
getMoyenne pour obtenir sa valeur. Voyons un exemple de méthode setMoyenne :
Entrées : un flottant moyenne,
Hypothèses complémentaires : ce flottant doit valoir soit –1 si la moyenne n’est pas attribuée, soit
être comprise entre 0 et 20 inclus.
Finalité : modifier la moyenne si la valeur en entrée est acceptable.
Sortie : Un booléen vrai si la valeur en entrée est acceptable, faux sinon,
Conclusion complémentaire : l’attribut moyenne est modifié si la valeur en entrée est
acceptable.
Stratégie : si la valeur en entrée est acceptable, modifier la valeur de l’attribut moyenne
Variable intermédiaire : aucune
Méthodes utilisées : aucune.
public boolean setMoyenne(float moyenne) {
if (moyenne != -1 && (moyenne <0 || moyenne > 20))
return false ;
this.moyenne = moyenne ;
return true
}
On aurait pu également insérer dans cet algorithme un appel à une méthode d’autorisation qui
demande un mot de passe, afin que seules les personnes autorisées aient doit d’accès à la
modification.
Le fait que la moyenne ne soit pas publique et donc que l’écriture E.moyenne soit illégale, nous
impose d’avoir une méthode pour obtenir sa valeur. Dans notre cas, cette méthode contiendra un
simple retour de valeur (voir une demande d’autorisation) :