I. Introduction▲
Plusieurs parmi nous (je suis le premier) ont appris le langage Java en rassemblant des bouts de code dans le but de réaliser un logiciel (ce que l'on a coutume d'appeler formation sur le tas). Quoique cela a toujours fini par donner un résultat, on ne saurait cependant nier son inconvénient majeur : à la fin on ne sait jamais comment les choses fonctionnent au fond. Ce qui peut avoir pour conséquences : la mauvaise utilisation des types d'objets et de la mémoire qui parfois occasionne des pertes en temps énormes suite à une mauvaise approche lors du débogage.
À la suite du premier chapitre qui traitait des énumérations, des classes, des interfaces, des règles sur les identifiants et du standard JavaBean, ce chapitre (qui est le deuxième de la suite le mémo du certifier Java SE 6) va s'attarder sur le concept de programmation orientée objet en prenant le soin d'élucider les mécanismes qui gravitent tout au tour. Comme mécanismes, nous parlerons de : l'encapsulation, l'héritage, la surcharge des méthodes, la conversion des types, l'instanciation, du couplage et de la cohésion. À l'issue de cette présentation, cinq exercices corrigés sont mis à la disposition du lecteur pour des besoins d'évaluation personnelle. Je conseille vivement que ce test soit effectué avant et après la lecture de l'article.
II. Encapsulation▲
L'encapsulation est un mécanisme permettant de masquer l'implémentation des objets tout en les rendant accessibles à travers un certain nombre de services ou interfaces. Du fait qu'elle permette de définir les interfaces de manipulation de données au sein d'un objet, l'encapsulation constitue un gage pour l'intégrité des données.
- L'encapsulation favorise la maintenabilité, la flexibilité et l'extensibilité des applications. Pour la mettre en œuvre, il faut :
- Grâce aux méthodes d'accès (les getters et les setters), il vous sera possible de modifier votre code sans avoir besoin de faire des ajustements supplémentaires sur les classes qui l'utilisent.
- Les variables locales résident dans le stack.
- Faire précéder les variables d'instance du mot-clé protected ou private ;
- Définir des accesseurs public et obliger les objets extérieurs à modifier ou à accéder aux variables d'instance uniquement à travers ses accesseurs. Il ne faut pas que les objets extérieurs puissent directement accéder aux variables d'instance ;
- Pour les méthodes d'accès aux variables d'instance, il faut utiliser les conventions de nommage JavaBeans (setXxx and getXxx ).
III. L'héritage , Is-A, Has-A▲
- instanceof retourne true si la variable de référence à tester est du même type que la classe utilisée pour faire la comparaison.
Test t =
new
Test
(
); // t est la variable de référence
if
(
t instanceof
Object) //Object est la classe pour la comparaison
System.out.println
(
"OK"
);
//le résultat est OK
- Toutes les classes Java sont des sous-classes de la classe Object. Excepté la classe Object elle-même. C'est pour cette raison que toutes les classes en Java contiendront toujours les méthodes equals(), clone(), notify(), wait()…
- L'utilisation de l'héritage favorise :
- la réutilisation du code ;
- la mise en œuvre du polymorphisme.
- Dans le monde de l'Orienté Objet (OO), le concept de “Is-A” est basé sur l'héritage ou des classes ou l'implémentation des interfaces. Et cela est matérialisé par les mots-clés : extends pour l'héritage des classes et implements pour l'implémentation des interfaces.
- La relation “Has-A” est basée sur l'utilisation plutôt que l'héritage. Une classe A “Has-A” B si la classe A possède une référence vers une instance de la classe B. Dans l'exemple ci-dessous, on dira que Voiture “Has-A” Roue et Rav4 “Is-A” Voiture.
public
class
Voiture {
Roue roue;
}
class
Roue {
}
class
Rav4 extends
Voiture {
}
- La relation “Has-A” permet d'élaborer des classes en suivant de bonnes pratiques de l'orienté objet. Car cela évite d'avoir des classes monolithiques qui contiennent tout. Cela facilite aussi la réutilisation du code.
IV. La réécriture et la surcharge des méthodes▲
IV-A. La réécriture des méthodes▲
Lorsqu'on hérite d'une classe, on hérite aussi de son comportement et de ses caractéristiques. Il peut donc arriver pour une raison ou une autre de vouloir changer le mode opératoire d'une méthode. Alors, pour remplacer l'implémentation de la méthode héritée, on va procéder à une réécriture.
- À chaque fois que vous avez une classe qui étend une autre, vous avez la possibilité de réécrire des méthodes, à moins que ces méthodes soient final, private ou static.
- La réécriture d'une méthode ne doit pas avoir un modificateur d'accès plus restrictif que celui de la méthode réécrite.
public
class
Toyota extends
Voiture{
//erreur de compilation le modificateur doit être public
protected
void
rouler
(
){}
public
void
accelerer
(
){}
//OK
//erreur de compilation le modificateur doit être public, default ou protected
private
void
freiner
(
){}
protected
void
virer
(
){}
//OK
}
class
Voiture {
public
void
rouler
(
){}
private
void
accelerer
(
){}
void
freiner
(
){}
void
virer
(
){}
}
- Les règles concernant la réécriture des méthodes :
- La liste des arguments de la réécriture doit être exactement la même que celle de la méthode réécrite. Sinon il s'agira d'une surcharge au lieu d'une réécriture ;
- Le type de retour doit être le même ou un sous-type du type déclaré dans la méthode réécrite ;
class
Fabriquant {
public
Voiture fabriquer
(
){
return
new
Voiture
(
);}
}
class
Toyota extends
Fabriquant{
public
Rav4 fabriquer
(
){
return
new
Rav4
(
); }
}
class
Mazda extends
Fabriquant {
public
Runner fabriquer
(
){
return
new
Runner
(
); }
}
class
Voiture{}
class
Rav4 extends
Voiture {}
class
Runner extends
Voiture {}
-
- Le modificateur d'accès ne doit pas être plus restrictif ;
- On parle de réécriture de méthode uniquement lorsqu'il y a héritage ;
- La réécriture d'une méthode peut lever de nouvelles exceptions du type “Runtime Exception” même si la méthode réécrite ne l'a pas déclarée ;
class
Fabriquant {
public
Voiture fabriquer
(
){
return
new
Voiture
(
);
}
}
class
Toyota extends
Fabriquant{
public
Voiture fabriquer
(
) throws
RuntimeException{
return
new
Voiture
(
);
}
//une erreur de compilation se produit si on décommente la méthode
//public Voiture fabriquer() throws Exception{ return new Voiture();}
}
class
Voiture{}
-
- La réécriture d'une méthode ne doit pas lever une exception du type “checked exception” qui ne soit pas déclarée par la méthode réécrite ou qui n'ait pas de parent parmi les exceptions déclarées au niveau de la méthode réécrite ;*
class
Toyota extends
Voiture{
public
void
rouler
(
) throws
ArithmeticException,
NullPointerException{}
//OK exception du type RuntimeException
/*
erreur de compilation, exception du type checked
exception non déclarée dans la super-méthode
*/
public
void
accelerer
(
) throws
ClassNotFoundException {
}
// OK, sous-classe de la classe Exception
public
void
virer
(
) throws
ClassNotFoundException{}
}
class
Voiture{
public
void
rouler
(
){}
public
void
accelerer
(
){}
public
void
virer
(
) throws
Exception{}
}
-
- La réécriture d'une méthode peut lever moins d'exceptions que la super-méthode. Ceci s'explique par le fait qu'il est possible de traiter dans la réécriture des exceptions levées au niveau de la super-méthode ;
class
Toyota extends
Voiture{
public
void
rouler
(
) {}
//ok
}
Public class
Voiture{
public
void
rouler
(
) throws
IOException, ParseException{}
}
-
- On ne peut pas réécrire une méthode final ;
- On ne peut pas réécrire une méthode static ;
- Si une méthode ne peut pas être héritée (par exemple les méthodes private), elle ne peut non plus être réécrite.
class
Animal{
public
void
eat
(
){
System.out.println
(
"Animal"
);}
}
class
Horse extends
Animal {
public
void
eat
(
){
super
.eat
(
);
System.out.println
(
"Horse"
);
}
}
- En Java, il n'existe pas de réécriture de variable. Ainsi, il est possible de déclarer des variables de même nom dans une sous-classe et dans la classe parent sans qu'il y ait interférence.
class
Animal{
int
age =
6
;
}
class
Horse extends
Animal {
int
age =
10
;
public
void
eat
(
){
Horse h =
new
Horse
(
);
Animal a =
new
Horse
(
);
System.out.println
(
"ageH : "
+
h.age+
" ageA : "
+
a.age);
}
}
//ageH : 10 ageA : 6
IV-B. La surcharge des méthodes▲
Il s'agit d'un mécanisme permettant d'avoir au sein d'une classe, des méthodes/constructeurs ayant le même nom avec des arguments de type différent. Ainsi, on dira qu'il y a surcharge de méthodes/constructeurs dans une classe, lorsque deux méthodes au moins posséderont le même nom avec chacune des particularités au niveau du type d'argument pris en paramètre.
class
Horse {
public
void
eat
(
){
}
public
void
eat
(
String food){
}
public
void
eat
(
int
age){
}
}
- Les règles concernant la surcharge des méthodes :
- les méthodes qui participent à la surcharge ne doivent pas avoir les mêmes listes d'arguments ;
class
Horse {
public
void
eat
(
){
}
public
void
eat
(
String food){
}
public
void
eat
(
String water){
}
//erreur à la compilation
public
void
eat
(
String food, int
age){
}
}
-
- les méthodes d'une surcharge peuvent avoir des types retour distincts ;
- les méthodes d'une surcharge peuvent avoir des modificateurs d'accès différents ;
- les méthodes d'une surcharge peuvent déclarer des exceptions de façon indépendante ;
- une méthode peut être surchargée dans la même classe ou dans une sous-classe. Ceci dit, deux méthodes de même nom, mais contenues dans différentes classes peuvent toujours être considérées comme surchargées.
class
Animal{
public
void
eat
(
){}
}
class
Horse extends
Animal {
protected
Horse eat
(
int
age) throws
Exception{
}
public
int
eat
(
String food){
}
private
Animal eat
(
String food, int
age) throws
RuntimeException{
}
}
IV-C. Appel des méthodes surchargées▲
- L'appel d'une méthode surchargée est fonction du type de la référence et non du type de l'objet.
class
Animal{}
class
Horse extends
Animal {}
class
TheAnimals {
void
eat
(
Animal a){
System.out.printl
(
"Animal"
);}
void
eat
(
Horse h){
System.out.printl
(
"Horse"
);}
public
static
void
main
(
String[] args){
TheAnimals us =
new
TheAnimals
(
);
Animal an =
new
Animal
(
);
Animal anref =
new
Horse
(
);
Horse hor =
new
Horse
(
);
us.eat
(
an);//affiche Animal
us.eat
(
anref);//affiche Animal
us.eat
(
hor);//affiche Horse
}
}
- Même si à l'exécution anref est de type Horse, le choix de la méthode surchargée à appeler est déterminé à la compilation en fonction du type de référence qui est Animal pour le cas d'espèce.
- De manière générale, l'appel d'une version de méthodes réécrite est déterminé à l'exécution de l'application en fonction du type des objets. Tandis que, l'appel d'une version de méthodes surchargée est déterminé à la compilation en fonction du type de référence des paramètres.
- Quelques différences entre la réécriture et la surcharge :
Méthode surchargée | Méthode réécrite | |
La liste des arguments | Doit changer | Ne doit pas changer |
Le type de retour | Peut changer | Doit être le même ou un sous-type du type déclaré dans la méthode à réécrire |
Exceptions | Peuvent changer | Peuvent réduire ou éliminer les exceptions. Ne doivent pas lever de nouvelles exceptions de type « checked» |
Modificateur d'accès | Peut changer | Il ne doit pas être plus restrictif que celui de la méthode à réécrire |
Invocation | Elle s'appuie sur le type de référence à la compilation | Elle s'appuie sur le type d'objet à l'exécution |
V. Conversion et accès aux variables d'une classe▲
V-A. Conversion des types de variables (casting)▲
class
Animal {}
class
Dog extends
Animal {
void
playDead
(
){}
}
If you tried :
Animal a =
new
Dog
(
);
a.playDead
(
); //you will get a compile error : cannot find symbol
But if
you do
something like :
Dog d =
(
Dog)animal; //this code compiles! when you try to run it we'll get an exception ClassCastException
Animal a =
new
Animal
(
);
String s =
(
String)animal;//animal can never be a String we'll get compile error like inconvertible types.
V-B. Méthodes et variables statiques▲
- La variable static d'une classe est partagée par toutes les instances de cette classe.
public
class
Animal {
static
int
speed;
public
static
void
main
(
String[] args){
Animal an1 =
new
Animal
(
);
an1.speed++
;
Animal an2 =
new
Animal
(
);
an2.speed++
;
Animal an3 =
new
Animal
(
);
an3.speed++
;
System.out.println
(
"speed : "
+
speed);//le resultat est 3
}
}
- Une méthode static ne peut pas directement accéder aux variables ou aux méthodes non static.
- On peut accéder aux objets (variables et méthodes) static d'une classe en faisant : «nom_de_la_classe ».«nom_objet_static» ou «référence_objet».«nom_objet_static».
public
class
Animal {
static
int
speed;
public
static
void
main
(
String[] args){
//ces deux appellations sont identiques
System.out.println
(
Animal.speed);//nom_de_la_classe.nom_objet_static
Animal an =
new
Animal
(
);
System.out.println
(
an.speed);//référence_objet.nom_objet_static
}
}
- Une méthode static ne peut pas être réécrite dans une sous-classe, mais elle peut être redéfinie.
public
class
Animal {
public
static
void
manger
(
){
System.out.println
(
"Animal"
);
}
public
static
void
main
(
String[] args){
Animal a =
new
Animal
(
);
a.manger
(
);//imprime Animal
a =
new
Lion
(
);
a.manger
(
);//imprime Animal au lieu de Lion
Lion l =
new
Lion
(
);
l.manger
(
);//imprime Lion
}
}
class
Lion extends
Animal {
public
static
void
manger
(
){
System.out.println
(
"Lion"
);
}
}
VI. Implémentation des interfaces▲
- Afin d'implémenter une interface, une classe non abstract doit :
- fournir une implémentation concrète pour toutes les méthodes déclarées dans l'interface ;
- respecter toutes les règles de la réécriture des méthodes.
- Une classe non abstract peut aussi implémenter une interface. Dans ce cas, la classe n'est pas obligée de fournir une implémentation concrète des méthodes de l'interface.
- Une classe peut implémenter plus d'une interface mais ne peut étendre qu'une seule classe.
- Une interface peut étendre une autre interface, mais ne peut rien implémenter d'autre.
interface
Voiture {
public
void
rouler
(
);
}
interface
Toyota extends
Voiture {}
abstract
class
Rav4 implements
Toyota {}
public
class
Rav4NewDesign extends
Rav4 {
public
void
rouler
(
){
}
}
VII. Type retour des méthodes▲
VII-A. Déclaration des types de retour▲
- Pour une méthode surchargée, il n'y a pas de restriction sur le type de retour. Rappelez-vous que pour surcharger une méthode, vous avez seulement besoin de changer les arguments qu'elle prend en paramètre. Ainsi, une méthode surchargée est complètement différente de ses consœurs.
- Pour une méthode réécrite, il est possible de changer le type de retour tant que le nouveau type est un sous-type du type retourné par la méthode réécrite.
VII-B. Le retour des valeurs▲
- Il y a principalement cinq règles à respecter pour le retour des valeurs :
- On ne peut retourner null que pour des méthodes qui retournent des types d'objet (à l'opposé des primitives) ;
class
Voiture {
public
int
vitesse
(
){
//elle retourne une primitive
return
null
;//erreur de compilation
}
public
Integer kilometrage
(
){
//elle retourne un objet (un wrapper)
return
null
;//ok
}
}
-
- Il est légal de retourner des tableaux d'objets ou de primitives ;
- Pour une méthode qui retourne une primitive, il est possible de retourner une valeur ou une variable qui peut implicitement être convertie au type de retour déclaré. Dans le cas contraire, il faudra d'abord convertir la valeur à retourner ;
class
Voiture {
public
int
vitesse
(
){
char
a =
'a'
;
return
a;
}
public
int
vitesse
(
double
speed){
return
(
int
) speed;
}
}
-
- On ne doit rien retourner pour une méthode qui retourne le type void ;
class
Voiture {
//les deux méthodes sont correctes
public
void
vitesse
(
){
return
;
}
public
void
vitesse
(
double
speed){}
}
-
- Pour une méthode qui retourne un objet, il est possible de retourner tout objet qui peut implicitement est converti au type de retour déclaré ;
interface
Voiture {}
class
Toyota implements
Voiture{}
class
Mazda implements
Voiture{}
public
class
Fabrication {
public
Voiture fabriqueToyota
(
){
Toyota t =
new
Toyota
(
);
return
t;
}
public
Voiture fabriqueMazda
(
){
Mazda m =
new
Mazda
(
);
return
m;
}
}
VIII. Les constructeurs▲
En Programmation Orientée Objet (POO), un constructeur est une méthode « spéciale » d'une classe qui permet de créer une instance d'un objet (instancier), d'allouer la mémoire nécessaire à l'objet et d'initialiser ses attributs.
VIII-A. Les constructeurs et l'instanciation▲
- Toute classe, y compris les classes abstract doivent avoir au moins un constructeur.
- Un constructeur n'a aucun type retour et son nom doit exactement être le même que celui de la classe qui l'abrite.
public
class
Animal {
public
Animal
(
){}
//ok
public
Animal
(
Object obj){}
//ok
public
animal
(
){}
//non,il faut respecter la casse
public
void
Animal
(
){}
//c'est une méthode légale pas un constructeur
}
- Les constructeurs sont généralement utilisés pour initialiser les variables d'instance.
- Un constructeur est invoqué à l'exécution lorsqu'on utilise le mot-clé new pour l'instance d'un objet : Animal a = new Animal();.
- Le déroulement du processus de création d'une instance.
- L'instanciation d'un objet en Java suit plusieurs étapes. Pour comprendre ce processus, considérons la création d'un objet Bmwv de l'exemple suivant :
class
Voiture {
public
Voiture
(
){
System.out.print
(
"-Voiture-"
);
}
}
public
class
Bmwv extends
Voiture{
public
Bmwv
(
){
System.out.print
(
"-Bmwv-"
);
}
public
static
void
main
(
String... args){
new
Bmwv
(
);
}
}
//resultat : -Voiture--Bmwv-
-
- le constructeur Bmwv() est invoqué ;
- avant que la méthode print() ne soit exécutée, le constructeur Voiture() est invoqué. Ici, il est important de noter que, tout constructeur invoque toujours le constructeur de la super-classe de façon explicite à travers la méthode super() ou de façon implicite, à moins que le constructeur n'invoque un constructeur surchargé de la même classe. Dans notre cas, Voiture est la super-classe de Bmwv ;
- le constructeur de la classe Object est invoqué car, la classe Object est la super-classe de toutes les classes ;
- les variables d'instance de la classe Object sont initialisées ;
- le constructeur de la classe Object est entièrement exécuté ;
- les variables d'instance de la classe Voiture sont initialisées (assignation des valeurs par défaut) ;
- le constructeur de la classe Voiture est entièrement exécuté. Dans notre cas, c'est ici que sera imprimé le texte «-Voiture-» ;
- les variables d'instance de la classe Bmwv sont initialisées (assignation des valeurs par défaut) ;
- enfin, le constructeur de la classe Bmwv est entièrement exécuté. Et c'est ici que sera imprimé le texte «-Bmwv-». D'où le résultat.
-
- un constructeur peut avoir n'importe quel modificateur d'accès, y compris private (sauf que, dans le cas d'un modificateur d'accès private, la classe doit fournir une méthode static ou une variable qui permettra l'accès à l'instance créée au sein de la classe) ;
- le nom du constructeur doit être le même que celui de la classe qui l'abrite ;
- un constructeur ne doit pas avoir de type retour ;
- il est possible d'avoir une méthode ayant le même nom que la classe, mais cela ne fait pas de cette méthode un constructeur.
class
Voiture {
public
Voiture
(
){
System.out.print
(
"-Voiture-"
);
super
(
);//erreur de compilation, la méthode super doit venir en premier
}
public
Voiture
(
int
i){
// la méthode super(); sera insérée à la compilation à ce niveau
System.out.print
(
"-Voiture-"
);
}
public
Voiture
(
long
i){
this
(
2
);//OK, appel du constructeur qui prend un int en paramètre
}
}
- La méthode super() peut être appelée sans argument, mais il est aussi possible de lui passer des arguments en fonction des constructeurs définis dans la super-classe.
- Un constructeur sans argument n'est pas forcément un constructeur par défaut (constructeur généré par le compilateur).
- Il est impossible d'accéder à une méthode de classe ou une variable d'instance avant l'appel du super-constructeur.
class
Voiture {
public
Voiture
(
int
i) }
}
public
class
Bmwv extends
Voiture{
int
speed;
static
int
value;
public
Bmwv
(
){
super
(
speed);//erreur de compilation, speed est une variable d'instance
//super(value); OK car value est une variable static
}
}
- Uniquement des variables static et des méthodes static peuvent être passées en paramètre des appels à super() ou this().
- Les classes abstraites possèdent aussi des constructeurs et ces constructeurs sont toujours appelés lors du processus d'instanciation des sous-classes.
- Une interface ne possède pas de constructeur.
- L'unique façon de faire appel à un constructeur dans un autre c'est d'utiliser la méthode this(). En d'autres termes, il n'est pas possible d'appeler un constructeur comme suit :
class
Voiture {
public
Voiture
(
){}
public
Voiture
(
int
i){
Voiture
(
);//erreur de compilation
//il faut faire : this();
}
}
VIII-B. Conditions de création d'un constructeur par défaut▲
- Le compilateur crée le constructeur par défaut dans une classe lorsque ladite classe ne comporte aucun constructeur à la compilation.
- Les caractéristiques d'un constructeur par défaut :
- il possède le même modificateur d'accès que la classe qui le contient ;
- il n'a aucun argument ;
- il fait appel au super-constructeur sans argument (super()).
public
class
Animal {
protected
Animal
(
){
//ce n'est pas un constructeur par défaut
super
(
);
}
}
public
class
Animal {
public
Animal
(
){
//c'est un constructeur par défaut
super
(
);
}
}
- Si une super-classe ne contient que des constructeurs avec argument, vous êtes obligé de faire appel à la méthode super() en lui passant les arguments nécessaires dans tous les constructeurs des sous-classes.
class
Voiture {
public
Voiture
(
int
speed){}
}
public
class
Toyota extends
Voiture {
public
Toyota
(
){}
//erreur de compilation
public
Toyota
(
int
speed){
super
(
0
);//OK
}
}
- Si une super-classe ne contient que des constructeurs avec arguments, vous êtes obligé de définir des constructeurs dans toutes ses sous-classes et chaque constructeur devra faire appel à la méthode super() en lui passant les arguments nécessaires.
class
Voiture {
public
Voiture
(
int
speed){}
}
public
class
Toyota extends
Voiture {
//erreur de compilation
}
VIII-C. La surcharge des constructeurs▲
- Surcharger un constructeur signifie créer plusieurs versions de constructeurs avec des arguments différents.
public
class
Voiture {
public
Voiture
(
){}
public
Voiture
(
short
sieges){}
public
Voiture
(
int
speed){}
}
- Si vous voulez appeler un constructeur dans un autre, vous devez utiliser le mot-clé this.
class
Animal {
String name;
Animal
(
String name){
this
.name =
name}
Animal
(
){
this
(
"My name"
);}
//appel du premier constructeur
}
- La première instruction dans un constructeur doit être l'appel de la méthode super() ou this(). Si aucune de ces instructions n'est insérée, le compilateur va insérer la méthode super() sans argument.
- Un constructeur ne peut pas avoir à la fois un appel à super() et un appel à this(), car chacune de ses méthodes doit être la première instruction dans un constructeur.
- Un compilateur ne peut pas insérer la méthode super() dans un constructeur qui contient déjà la méthode this().
IX. Le couplage et la cohésion▲
L'objectif d'une bonne conception est de faciliter la création, la maintenance et l'évolution d'une application. Pour ce faire, elle devra favoriser un couplage lâche entre les entités et une forte cohésion de chacune des entités.
IX-A. Le couplage▲
- Le couplage ici permet d'exprimer le niveau de visibilité qu'une classe a sur une autre. Ainsi, deux classes seront fortement couplées si le changement d'une variable dans l'une nécessite impérativement un changement dans l'autre.
- Si deux classes s'échangent des données uniquement à travers des interfaces alors les deux classes sont dites faiblement couplées. Ce qui est une bonne chose. Dans le cas d'espèce, la classe Voiture et la classe Roue sont faiblement couplées. Ainsi, il devient possible de modifier l'attribut diamètre ou poids dans la classe Roue sans toutefois perturber la relation Voiture-Roue.
class
Voiture {
Roue roue;
public
void
montage
(
){
roue =
new
Roue
(
);
roue.setDiametre
(
12.5
);
roue.setPoids
(
19
);
}
}
class
Roue {
private
double
poids;
private
double
diametre;
public
double
getDiametre
(
) {
return
diametre;
}
public
void
setDiametre
(
double
diametre) {
this
.diametre =
diametre;
}
public
double
getPoids
(
) {
return
poids;
}
public
void
setPoids
(
double
poids) {
this
.poids =
poids;
}
}
- Si une classe A accède aux données d'une classe B sans passer par les interfaces de la classe B alors les classes A et B sont dites fortement couplées. Ce qui n'est pas une bonne chose pour une application.
class
Voiture {
Roue roue;
public
void
montage
(
){
roue =
new
Roue
(
);
roue.diametre =
12.5
;
roue.poids =
19
;
}
}
class
Roue {
public
double
poids;
public
double
diametre;
}
IX-B. La cohésion▲
- Alors que le couplage concerne les interactions entre les classes, la cohésion se focalise sur la conception interne d'une classe.
- La cohésion permet d'exprimer le niveau de conception d'une classe en tant qu'entité indépendante. Cela nécessite l'encapsulation des données et le masquage des informations.
- Une classe ayant une forte cohésion est facilement maintenable et réutilisable.
X. Exercices▲
X-A. Corrigez les erreurs et trouvez le résultat▲
class
Animale {
public
Animale
(
String nom) {
System.out.print
(
"B"
); }
}
public
class
Chien implements
Animale {
public
Chien
(
String nom) {
System.out.print
(
"A"
); }
public
static
void
main
(
String ... args) {
new
Chien
(
"C"
);
System.out.println
(
" "
); }}
X-B. Corrigez les erreurs et trouvez le résultat▲
class
Animale {
private
final
void
manger
(
) {
System.out.println
(
"Animale"
); }
}
public
class
Chien extends
Animale {
public
final
void
manger
(
) {
System.out.println
(
"Chien"
); }
public
static
void
main
(
String [] args) {
new
Chien
(
).manger
(
);
}
}
X-C. Corrigez les erreurs et trouvez le résultat▲
class
Animale {
public
void
manger
(
) {
System.out.println
(
"Animale"
);}
}
class
Chien extends
Animale {
void
manger
(
) {
System.out.println
(
"Chien"
);}
void
aboie
(
) {
System.out.println
(
"Chien-aboie"
);}
}
class
Courrir {
public
static
void
main
(
String [] args) {
Animale a1 =
new
Chien
(
);
Animale a2 =
new
Chien
(
);
Chien c =
new
Chien
(
);
a1.manger
(
);
a1.manger
(
);
a1.aboie
(
);
c.manger
(
);
c.aboie
(
);
}
}
X-D. Corrigez les erreurs et trouvez le résultat▲
public
class
Chien extends
Animale {
public
static
String manger
(
) {
return
"viande"
; }
public
static
void
main
(
String args[]) {
Chien c1 =
new
Chien
(
);
Animale a1 =
new
Chien
(
);
Animale a2 =
new
Animale
(
);
System.out.println
(
c1.manger
(
) +
" "
+
a1.manger
(
) +
" "
+
a2.manger
(
));
}
}
class
Animale {
public
static
String manger
(
) {
return
"nourriture"
; }
}
X-E. Corrigez les erreurs et trouvez le résultat▲
class
Animale {
}
class
Chien implements
Animale {
}
public
class
Foret {
String s =
"-"
;
public
static
void
main
(
String[] args) {
Animale[] a =
new
Animale[2
];
Chien[] c =
new
Chien[2
];
chercher
(
a);
chercher
(
c);
chercher
(
7
);
System.out.println
(
s);
}
static
void
chercher
(
Animale[]... a) {
s +=
"1"
; }
static
void
chercher
(
Chien[]... b) {
s +=
"2"
; }
static
void
chercher
(
Chien[] b) {
s +=
"3"
; }
static
void
chercher
(
Object o) {
s +=
"4"
; }
}
XI. Correction des exercices▲
XI-A. Exercice 1▲
- Erreurs
On implémente une interface et on étend une classe. Il faut donc utiliser le mot-clé extends entre Chien et Animale
Lorsqu'une classe mère n'a pas de constructeur sans argument, la classe fille doit explicitement faire appel à la méthode super() dans tous ses constructeurs en lui passant les paramètres nécessaires. Dans notre cas, nous allons appeler la méthode super() avant l'instruction System.out.print("A"); en lui passant le nom de l'animal comme paramètre.
- Solution
Le résultat après correction des anomalies est : BA. Car, à l'instanciation de la classe Chien, le constructeur de la classe mère (Animale) sera entièrement exécuté avant celui de la classe fille (Chien).
XI-B. Exercice 2▲
- Erreurs
Il n'y a aucune erreur. Par contre, si la méthode manger () de la classe Animale était public, defaultou protected, un problème allait se poser parce que l'on ne peut réécrire une méthode final.
- Solution
La solution est : Chien
XI-C. Exercice 3▲
- Erreurs
La réécriture d'une méthode ne doit pas avoir un modificateur d'accès plus restrictif que celui de la méthode réécrite. Ici, la méthode manger() de la classe Chien doit avoir public comme modificateur d'accès .
L'appel d'une version de méthode réécrite est déterminé à l'exécution de l'application en fonction du type des objets et non du type de référence. Dans le cas présent, pour la variable a1 le type d'objet est Chien et le type de référence est Animale. Or la classe animale ne possède pas de méthode aboie(). D'où il faudra commenter l'instruction a1.aboie();
- Solution
La solution est :
Chien
Chien
Chien
Chien - aboie
XI-D. Exercice 4▲
- Erreurs
Il n'y a aucune erreur.
- Solution
La solution sera : viande nourriture nourriture. Il faut se rappeler que l'on ne peut pas réécrire une méthode static. Ceci dit, l'instruction a1.manger() fait appel à la méthode manger() de la classe Animale au lieu de la classe Chien qui est le type de l'objet.
XI-E. Exercice 5▲
- Erreurs
On implémente une interface et on étend une classe. Il faut donc utiliser le mot-clé extends entre Chien et Animale.
On ne peut accéder aux variables non-static dans une méthode static. Il faut donc déclarer la variable « s » comme static.
- Solution
Le résultat est : -434
Conclusion▲
Le prochain article traitera principalement des variables. On verra entre autres, la création, l'initialisation, le transtypage, le passage en paramètre et la suppression des variables. Mais avant de nous quitter je voudrais adresser mes remerciements à ClaudeLELOUPClaudeLELOUPetRobin56Robin56 pour la relecture orthographique, et à keulkeulkeulkeul pour ses propositions, ses encouragements et le temps consacré pour les diverses relectures.
Pour réaliser cet article, j'ai fait usage de deux livres que je trouve complémentaires et que je n'hésite pas à vous conseiller. Il s'agit de :
- SCJP Sun Certified Programmer for Java 6 Study Guide de Kathy Sierra et Bert Bates ;
- A Programmer's Guide to Java™ SCJP Certification Third Edition de Khalid A. Mughal et Rolf W. Rasmussen.
Suivez moi sur twitter