V - Java et les objets

publicité
V - Java et les objets
Comme nous l'avons déjà dit à plusieurs reprises, Java est un véritable langage orienté objet. Dans
ce chapitre, nous allons tenter de présenter les principaux concepts de la programmation orientée
objet tout en voyant comment ces concepts sont appliqués en Java.
1. Introduction à la POO
La Programmation Orientée Objet (POO) est souvent introduite en la comparant à la
programmation procédurale.
En C, par exemple, quand on conçoit un programme, on le "découpe" en plusieurs modules ou
fonctions ayant chacun un rôle précis. Le but est en effet de décomposer un problème compliqué
(que le programme doit résoudre) en une succession de petits problèmes simples à traiter.
La modularité ainsi introduite est quelque peu artificielle et ne permet pas une réutilisabilité aisée
du code produit. C'est pourquoi on s'est intéressé à une autre façon de concevoir un logiciel : au
lieu de l'articuler autour de ses fonctionnalités qui, par définition, sont susceptibles d'évoluer de
façon importante au cours du cycle de vie d'un produit, on conçoit le logiciel en partant des
données qu'il manipule. Ces données étant généralement plus stables, cela facilite l'évoluabilité du
logiciel.
Avec Java, nous allons donc travailler avec des objets. Un objet est une entité qui possède des
attributs et à laquelle on peut envoyer des messages. Ces messages correspondent en fait à
l'exécution (ou invocation) d'une méthode (ou fonction) propre à l'objet.
2. Les classes
En POO, l'élément de base est la classe. Une classe est une sorte de "moule à objets". Avant de
pouvoir créer un objet, on définit sa classe d'appartenance. On dit qu'un objet est une instance d'une
classe. Une classe peut servir à créer plusieurs objets ou même d'autres classes, mais nous en
reparlerons plus bas.
Nous avons vu plus haut qu'un objet possédait des attributs. Il s'agit en fait de variables, définies
dans la classe dont est issu l'objet considéré. Les méthodes quant à elles ne sont rien d'autre que des
fonctions, ayant pour but d'effectuer des traitements, susceptibles ou non de modifier les attributs
d'un objet. Il est important de remarquer que quand on programme en Java, on code des classes,
pas des objets.
Afin de faciliter certains concepts que nous introduirons plus loin, définissons un formalisme
graphique pour représenter une classe.
Dans ce graphique, qui représente une classe voiture, on indique les attributs de la classe dans la
partie centrale du diagramme. La zone inférieure contient les méthodes.
Avant d'aller plus loin, voyons comment on définit une classe en Java. En fait, on utilise le même
mot clé qu'en C++, à savoir class. Voici la syntaxe de définition d'une classe de base :
class NomDeLaClasse {
// données membres
// méthodes
}
Reprenons l'exemple de la classe voiture et voyons comment on l'implémente en Java :
class Voiture {
// données membres
int Couleur;
int Vitesse;
// méthodes
void AugmenteVitesse()
{
if (Vitesse<5)
Vitesse++;
}
void DiminueVitesse()
{
if (Vitesse>0)
Vitesse--;
}
}
Dans cet exemple, on définit deux variables membres, Couleur et Vitesse, et deux fonctions
membres, AugmenteVitesse() et DiminueVitesse(). Nous allons examiner successivement les
caractéristiques des données et des fonctions membres.
3. Les données membres
Examinons l'exemple précédent de plus près. Nous constatons tout d'abord que les variables n'ont
pas été initialisées alors que l'une d'entre elles est utilisée dans les fonctions et que nous avons dit
plus haut qu'en Java, on devait affecter une valeur à toute variable avant de pouvoir l'utiliser...En
fait, cette règle ne s'applique pas aux données membres, car celles-ci sont initialisées
automatiquement à la création d'un objet à partir de la classe (nous étudierons la création d'objets
plus loin).
D'autre part, nous constatons que ces variables sont accessibles par les méthodes, sans qu'il ne soit
nécessaire de les passer en argument. C'est une des propriétés fondamentales des données membres
: toute fonction membre d'une classe peut accéder aux données qu'elle contient, nous en reparlerons
également plus bas.
4. Les fonctions membres
Avant d'examiner le cas des fonctions membres d'une classe, rappelons quelques définitions
concernant les fonctions.
Une fonction est une portion de code à laquelle on associe :
un nom,
une liste éventuelle d'arguments (i.e. variables auxquelles on veut pouvoir accéder dans la
fonction),
un type de retour.
Le nom d'une fonction doit être unique et doit respecter les règles de dénomination précisées pour
les noms de variables.
Les arguments sont indiqués entre parenthèses après le nom de la fonction. S'il n'y a aucun
argument, la liste est vide : (). Chaque argument est spécifié par son type et le nom de la variable,
s'il y en a plusieurs, on les sépare d'une virgule.
Enfin, le type de retour est un des types que nous avons vu précédemment pour les variables, bien
qu'on puisse retourner autre chose qu'une valeur numérique, comme nous le verrons plus loin. Une
fonction qui ne renvoie rien est du type void.
La spécification du nom, type de retour et arguments d'une fonction s'appelle la signature de la
fonction.
Exemple:
void AugmenteVitesse()
{
// variable locale
int Var=0;
// code de la fonction
}
Dans cet exemple, on a défini une fonction AugmenteVitesse qui ne prend aucun argument et qui
ne renvoie rien. On a défini dans cette fonction une variable Var, de type int, dite variable locale.
Cette variable n'est accessible qu'à l'intérieur de la fonction dans laquelle elle est définie. Cette
variable n'étant pas une donnée membre d'une classe, nous devons l'initialiser explicitement avant
de pouvoir l'utiliser, comme nous l'avons dit plus haut.
Prenons un deuxième exemple :
int CalculeModuleAuCarre(int x, int y)
{
int resultat=0;
resultat=x*x+y*y;
return resultat;
}
Il s'agit cette fois-ci d'une fonction qui prend deux entiers x et y en argument et qui renvoie une
valeur de type int. On a défini une variable locale resultat, utilisée pour stocker le résultat du calcul
du module au carré d'un nombre complexe, pour lequel cette fonction a été écrite. Celle-ci renvoie
le résultat de ce calcul grâce au mot clé return. Ce mot clé indique que l'on désire quitter la
fonction. S'il est suivi d'un argument, c'est la valeur de celui-ci qui est renvoyée au module
appelant. Remarquons que dans l'exemple précédent, nous n'avions pas utilisé de return car la
fonction en question retournait un void. Nous aurions pu mettre un return sans argument, cela
aurait provoqué le même effet.
Avant de revenir aux fonctions membres, précisons un point très important, propre au langage
Java. Il faut en effet savoir qu'il y a principalement deux façons de passer une variable en argument
d'une fonction : par valeur et par référence.
Lorsque l'on passe une variable par valeur, on obtient en fait une copie de celle-ci. Si on la modifie
dans la fonction, cela n'aura aucune incidence sur la valeur de la variable définie dans le module
appelant. Il est donc impossible de modifier la valeur de cette variable dans une fonction, en
utilisant ce mécanisme.
Par contre, lorsqu'on passe une variable par référence, cela veut dire que l'on va travailler sur la
même variable que celle définie dans le module appelant, passée en argument de la fonction. Il est
donc possible de modifier la valeur de cette variable, dans la fonction.
Certains langages, comme le C, permettent de passer l'adresse d'une variable en vue de permettre la
modification du contenu de celle-ci dans une fonction. On appelle ça un pointeur. En Java, on
entend souvent dire que les pointeurs n'existent pas et que les variables sont passées par référence.
En fait, ce n'est pas complètement exact : les pointeurs existent, mais ce qui n'existe pas c'est
l'arithmétique des pointeurs, qui permet de faire directement des calculs avec des adresses et qui
rend la programmation en C si délicate et "risquée".
Nous verrons plus loin comment appeler une fonction. Revenons aux fonctions membres en
programmation objet.
Une fonction membre d'une classe peut accéder :
aux variables locales qu'elle contient,
aux variables passées en argument,
aux variables membres de la classe.
Certaines fonctions membres d'une classe peuvent avoir un rôle particulier : celui d'initialiser les
données membres, avec d'autres valeurs que celles définies par défaut. De telles fonctions sont
appelées constructeurs. D'autres fonctions sont également un peu particulières en Java. Par
exemple, toute application écrite en Java se doit de posséder une méthode main() dans une de ses
classes. C'est cette méthode qui sera appelée en premier lors de l'exécution du programme. Pour les
applets, il n'y a pas de méthode main() mais il existe des équivalents. Nous en reparlerons dans le
chapitre consacré aux applets.
5. Constructeurs
Un constructeur est une fonction qui est appelée automatiquement à la création d'un objet de la
classe, et qui permet d'initialiser les données membres. Cette fonction a un nom précis : celui de la
classe.
Exemple :
class Exemple {
// donnée membre
int val;
// constructeur
Exemple()
{
val=1;
}
}
Ici, on a défini une méthode Exemple(), constructeur de la classe du même nom. Si on crée un
objet à partir de cette classe, la valeur de la variable val sera mise à 1 automatiquement, sans qu'il
ne soit nécessaire d'appeler la fonction Exemple.
A noter qu'un constructeur n'admet jamais aucun type de retour.
Nous avons dit plus haut que, par défaut, les données membres d'une classe sont initialisées à 0
quand on instancie (i.e. crée) un objet. En fait, dans ce cas précis, on fait appel à un constructeur
par défaut qui va initialiser les variables membres. Dans l'exemple précédent, nous avons défini un
constructeur, il ne sera donc plus possible d'appeler le constructeur par défaut.
Maintenant, si l'on souhaite pouvoir spécifier la valeur que l'on veut attribuer aux variables
membres à la création d'un objet, on doit définir un deuxième constructeur tel que celui-ci :
Exemple (int ValeurInit)
{
val = ValeurInit;
}
Remarque : Notez qu'il existe un mot clé this qui désigne l'objet courant, lorsque l'on veut, par
exemple, initialiser une variable membre portant le même nom que l'argument d'un constructeur :
Exemple (int val)
{
this.val = val;
}
Il est parfaitement possible de définir plusieurs constructeurs pour une même classe, à condition
qu'ils aient tous une signature différente (en fait, des arguments différents puisque le nom doit
toujours être celui de la classe pour un constructeur et qu'une telle fonction n'admet aucun type de
retour). L'opération permettant de définir plusieurs constructeurs du même nom s'appelle la
surdéfinition.
Lorsqu'une fonction a été surdéfinie, le compilateur saura quelle fonction appeler en examinant les
arguments d'appel de cette fonction (dans certains cas, le compilateur peut être amené à faire des
conversions de type, comme par exemple passer d'un short à un int).
Maintenant que nous avons présenté en détail le contenu d'une classe, il est grand temps de voir
comment on peut créer un objet à partir d'une classe.
6. L'instanciation et l'utilisation des objets
L'opération permettant de créer un objet à partir d'une classe s'appelle l'instanciation. On dit qu'on
instancie (i.e. on crée) un objet.
La création d'un objet consiste tout simplement à réserver un certain espace mémoire, contenant,
entre autres, les données membres définies dans la classe dont l'objet est issu.
Cependant, s'il est intéressant de pouvoir créer un objet, il est encore mieux de pouvoir l'utiliser.
Pour ce faire, on doit disposer d'une variable permettant d'y faire référence. Cette variable est
parfois appelée un handle, elle contient une référence à l'objet, mais ce n'est pas l'objet en luimême. Précisons ces notions grâce à un exemple :
Voiture Clio; // Ces 2 lignes sont équivalentes à
Clio = new Voiture(); // Voiture Clio = new Voiture();
Ici, on définit tout d'abord une référence à un objet de type Voiture. Cette référence s'appelle Clio.
Pour le moment, aucun objet n'a été créé. On a juste défini une référence pour un objet de type
Voiture.
C'est via la deuxième ligne que l'on va créer l'objet de type Voiture, accessible grâce à la variable
Clio. En Java, comme en C++, on utilise le mot clé new pour créer un objet à partir de la classe
spécifiée en argument.
Ce mot clé a pour effet de :
créer un objet,
appeler le constructeur de cet objet, s'il en existe un, ou utiliser celui par défaut,
renvoyer une référence à l'objet nouvellement créé, permettant ainsi de le manipuler.
Dans le cas de la classe Voiture, il n'existe pas de constructeur, c'est donc le constructeur par défaut
qui est appelé. Si on reprend le cas de la classe Exemple, il est possible d'appeler le deuxième
constructeur que nous avons défini plus haut, en procédant de la façon suivante :
Exemple expl = new Exemple(1997);
Cette fois-ci, on a passé la valeur 1997 en argument, cette valeur sera affectée à la donnée membre
val définie plus haut, dans la classe Exemple.
Voyons maintenant comment on peut manipuler un objet.
Manipuler signifie accéder aux données d'un objet ou appeler une méthode de cet objet (i.e. lui
envoyer un message). Dans les deux cas, on utilise en Java l'opérateur "." séparant le nom de l'objet
du nom du membre auquel on veut accéder.
Exemple :
Clio.Vitesse = 1;
Clio.DiminueVitesse();
A la première ligne, nous modifions la variable Vitesse de l'objet, en lui attribuant la valeur 1. A la
deuxième, nous appelons la méthode DiminueVitesse qui va donc remettre à 0 la variable Vitesse
considérée (rappelons que cette méthode diminue d'une unité la variable Vitesse).
Il est important de remarquer que, en Java, il n'existe pas de mot clé permettant de détruire un objet
qui n'est plus utilisé. Cette opération est en effet effectuée automatiquement par le garbage
collector, processus que nous avons présenté dans le chapitre dédié à la Machine Virtuelle Java.
Cela simplifie d'autant plus la programmation qu'il est alors impossible de désallouer par erreur un
objet toujours utilisé dans une autre partie d'un programme, comme cela pouvait être le cas en C++.
Dans l'exemple précédent, nous avons modifié une donnée membre et appelé une fonction membre.
De telles opérations ne sont pas toujours autorisées, grâce au principe de l'encapsulation que nous
verrons plus bas. Mais avant, examinons le cas des données et méthodes dites statiques.
7. Données et méthodes statiques
Par défaut, lorsque l'on crée deux objets à partir d'une même classe, chacun de ces objets possède
ses propres données membres. Il est cependant possible de définir en Java des variables qui sont
partagées entre tous les objets d'une même classe. Pour ce faire, on utilise le mot clé static.
Exemple :
class Velo {
int Reference;
static int NbreVelos=0; // variable statique
Velo()
{
Reference = 0;
NbreVelos++;
}
}
Ici, la variable NbreVelos a été déclarée statique. Si on crée deux instances de cette classe, par
exemple comme cela :
Velo velo1 = new Velo();
Velo velo2 = new Velo();
la variable NbreVelos vaudra la valeur 2 puisqu'elle a été incrémentée deux fois par le constructeur
de la classe Velo.
Une fonction membre qui manipule que des données statiques peut également être déclarée comme
étant statique.
Exemple :
static void DecrementeNbreVelo() // membre classe Velo
{
if (NbreVelos > 0)
NbreVelos--;
}
Une fonction statique a cependant une caractéristique importante : elle peut être appelée sans qu'il
ne soit nécessaire d'allouer un objet de la classe dans laquelle est définie cette fonction. Reprenons
l'exemple du calcul du carré du module vu plus haut, en précédant la fonction considérée du mot
clé static et en réduisant la fonction à l'essentiel :
int CalculeModuleAuCarre(int x, int y)
{
return (x*x+y*y);
}
Si cette fonction est membre de la classe Exemple, on pourra l'appeler sans qu'il ne soit nécessaire
de créer un objet de type Exemple, en précisant le nom de la classe à laquelle on fait référence :
mc = Exemple.CalculeModuleAuCarre(2, 3);
Passons maintenant à l'encapsulation.
8. L'encapsulation
L'encapsulation est un principe qui permet de limiter l'accès aux données et aux fonctions d'une
classe. C'est l'une des bases de la POO. Au niveau d'une classe donnée, on peut définir deux types
d'accès : public et privé.
Un membre public d'une classe est accessible par n'importe quelle méthode d'une autre classe. Pour
une donnée, cela signifie qu'on peut la modifier sans limite, pour une fonction, cela veut dire que
l'on peut toujours y faire appel. Pour indiquer au compilateur qu'un membre d'une classe est public,
on utilise le mot clé public.
Exemple :
class Publique {
// donnée publique
public int x;
// méthode publique
public void FonctionX()
{
// corps de la fonction
}
}
On peut alors faire les opérations suivantes, n'importe où dans un programme :
Publique pb = new Public();
pb.x = 34;
pb.FonctionX();
Déclarer des membres comme étant publics peut être particulièrement dangereux et peut entraîner
des difficultés de réutilisation du code. C'est pourquoi, on a défini une façon de protéger données
ou méthodes vis à vis de l'extérieur d'une classe. Pour ce faire, on utilise le mot clé private qui
indique qu'une donnée ou méthode membre est privée à la classe.
Exemple :
class Privee {
// donnée privée
private int x;
// fonction privée
private void FonctionX()
{
// corps de la fonction
}
}
Dans cet exemple, il devient impossible d'effectuer des opération du type :
Privee pr = new Privee();
pr.x = 34;
pr.FonctionX();
Il est cependant important de remarquer que la protection ainsi introduite ne s'applique qu'entre des
classes différentes, et non pas entre des objets d'une même classe.
Par exemple, si nous définissons une fonction FonctionDAcces comme suit, dans la classe
précédente :
void FonctionDAccess()
{
Privee pr = new Privee();
pr.x = 34;
pr.FonctionX();
}
il ne se produira ici aucune erreur car FonctionDAccess appartient à la même classe que la variable
x et que FonctionX.
En général, dans une classe, on définit un certain nombre de variables privées, qui ne sont pas
accessibles de l'extérieur, et un certain nombre de méthodes permettant de changer indirectement
ces données, en faisant appel si nécessaire à des méthodes privées.
Il existe d'autre types de protection, mais nous n'en dirons pas plus. Il nous reste cependant à
aborder une notion fondamentale de la programmation orientée objet: l'héritage.
9. L'héritage
Le principe de l'héritage est à la base de la réutilisabilité du code. C'est un mécanisme qui permet
de créer une classe à partir d'une autre classe, la première étant appelée classe parente et la
deuxième, sous-classe ou classe dérivée. Lorsqu'une classe hérite d'une autre, elle récupère les
données et fonctions membres de sa classe parente.
Une sous-classe peut non seulement ajouter des membres (données ou fonctions) à ceux dont elle a
hérité mais elle peut également redéfinir certaines de ses méthodes.
Prenons l'exemple classique d'une classe Forme (géométrique) dont hérite la classe Ligne. La
classe Forme comporte un attribut Couleur et une fonction Afficher. La classe Ligne possède les
attributs de Forme et y ajoute un attribut x et un attribut y. On peut représenter cela de la façon
suivante :
Dans cet exemple, la classe Ligne a redéfini la méthode Afficher afin de l'implémenter de façon
spécifique à l'affichage d'une ligne.
Voyons maintenant comment nous pouvons créer une telle classe Ligne en Java:
class Forme {
int Couleur;
void Afficher() {
// affichage standard
}
}
class Ligne extends Forme {
int x, y;
void Afficher() {
// code d'affichage d'une ligne
}
}
Dans cet exemple, on a utilisé le mot clé extends afin d'indiquer que la classe Ligne hérite de la
classe Forme. Un objet instancié à partir de la classe Ligne comprendra les données membres x, y
et Couleur, et la fonction Afficher, qui a été ici redéfinie dans la sous-classe. Il s'agit cette fois-ci
d'une redéfinition et non pas d'une surdéfinition, comme nous l'avons vu pour les constructeurs un
peu plus haut. En effet, dans le cas d'une surdéfinition, on change la signature de la fonction alors
que ce n'est pas le cas pour une redéfinition.
Afin de clarifier le mécanisme de l'héritage, prenons un exemple classique mais complet et
fonctionnel. Nous allons définir une classe, comprenant une méthode main.
// heritage.java
//
class heritage {
public static void main (String arg []) {
Forme f = new Forme ();
f.identification();
Ligne l = new Ligne ();
l.identification();
}
}
class Forme {
int Couleur;
// Constructeurs
Forme () {
Couleur = 0;
System.out.println("Appel du constructeur de Forme"); // Affiche à
l'écran la chaîne spécifiée
}
// Surdéfinition du constructeur
Forme (int coul) {
Couleur = coul;
System.out.println("Appel 2eme constructeur de Forme");
}
// Méthode d'identification
void identification () {
System.out.println("Classe Forme");
}
}
class Ligne extends Forme {
// Constructeur
Ligne() {
System.out.println("Appel au constructeur de Ligne");
}
// Redefinition de la méthode identification
void identification () {
System.out.println("Classe Ligne");
}
}
Ce programme définit trois classes :
une classe heritage permettant d'instancier des objets des deux autres classes. Elle contient une
méthode main(),
une classe Forme comprenant une donnée membre, deux constructeurs et une méthode
identification(),
une classe Ligne, dérivée de la classe Forme, redéfinissant la méthode identification() de le classe
Forme et comportant un constructeur.
Une fois compilé (javac heritage.java) et exécuté (java heritage), le programme affiche à l'écran le
texte suivant (nous avons mis le code correspondant en vis à vis afin de bien voir ce qu'il se passe
et à quel moment...) :
Appel du constructeur de Forme Forme f = new Forme ();
Classe Forme f.identification();
Appel du constructeur de Forme Ligne l = new Ligne ();
Appel au constructeur de Ligne
Classe Ligne l.identification();
Analysons cet affichage. Tout d'abord, on crée un objet à partir de la classe Forme. Le constructeur
de cette classe est alors appelé. Puis on appelle la fonction d'identification de la classe Forme.
Jusque là, rien de très particulier.
Ensuite, on instancie un objet de la classe Ligne. Cette fois, on constate que non seulement le
constructeur de la classe Ligne est appelé, mais également celui de sa classe parente, Forme. En
effet, quand on crée un objet d'une classe dérivée, le constructeur de la classe parente est appelé
automatiquement avant celui de la classe dérivée, ce qui est logique puisque ce constructeur doit
initialiser les variables dont hérite la sous-classe.
Enfin, la dernière ligne confirme que la méthode identification() a bien été redéfinie puisqu'elle ne
renvoie pas le texte spécifié dans la classe Forme. Si cette fonction n'avait pas été redéfinie, c'est
celle de la classe parente qui aurait été appelée.
Notre exemple renferme également un détail intéressant. Nous avons défini deux constructeurs
dans la classe Forme, un sans argument, un autre avec un argument. Nous avons vu que c'est le
premier qui avait été appelé. Mais comment peut-on faire si l'on désire que l'autre soit appelé à la
place ? Java possède un mot clé super qui permet d'effectuer ce genre d'opération. Réécrivons le
constructeur de la classe Ligne :
// Constructeur
Ligne() {
super (2);
System.out.println("Appel au constructeur de Ligne");
}
Pour faire appel au constructeur doté d'un argument, dans la classe Forme, on utilise la syntaxe
super (2). A noter qu'il est obligatoire de spécifier cette instruction en première ligne du
constructeur de la sous-classe. Si l'on exécute le programme ainsi modifié, voici ce que l'on obtient
à l'écran :
Appel du constructeur de Forme
Classe Forme
Appel 2eme constructeur de Forme
Appel au constructeur de Ligne
Classe Ligne
Comme prévu, c'est le deuxième constructeur de la classe Forme qui a été appelé.
Nous avons vu comment on héritait d'une classe et comment on redéfinissait une fonction. Mais
comment peut-on faire pour interdire la redéfinition d'une méthode ? Il a été prévu un mot clé
appelé final. Lorsque l'on précède la définition d'une méthode par ce mot clé, il devient impossible
de redéfinir la dite méthode.
Exemple :
final void identification () { // }
Cette fonction ne peut plus être redéfinie.
On peut également utiliser ce mot clé avec une donnée membre. Dans ce cas, il devient alors
interdit de modifier la donnée en question, que ce soit dans la classe où elle est définie ou dans une
sous-classe.
Exemple :
final int Couleur=0;
A noter qu'une variable finale est une constante, et elle doit donc être initialisée dans sa déclaration.
Remarque importante : Toutes les classes écrites par un programmeur ou issue de l'API Java
dérivent d'une seule et unique classe appelée Object.
10. Méthodes abstraites et interfaces
Nous avons vu que l'on pouvait interdire la redéfinition d'une méthode avec le mot clé final. Java
permet également de définir des méthodes sans corps, pour lesquelles on n'indique que la signature.
De telles méthodes sont dites abstraites. Une méthode abstraite doit obligatoirement être
(re)définie dans une sous-classe.
Le mot clé utilisé pour marquer une méthode comme étant abstraite est abstract.
Exemple :
abstract void MethodeAbstraite () ;
Il est important de noter qu'une telle fonction ne comporte pas de corps. De plus, à partir du
moment où une classe comporte au moins une fonction abstraite, cette classe doit également être
déclarée abstraite.
Exemple :
abstract class ClasseAbstraite {
abstract void MethodeAbstraite () ;
//...
}
Cette classe abstraite peut cependant contenir des méthodes qui ne le sont pas.
Une classe abstraite possède une propriété importante : elle ne peut pas être instanciée, ce qui est
logique puisqu'elle comprend des méthodes non implémentées, elle ne peut donc qu'être dérivée.
Java introduit un autre concept qui dépasse celui des classes abstraites : les interfaces. Une
interface est une classe dont toutes les méthodes sont abstraites et dont toutes les données membres
sont finales. On ne peut bien entendu pas instancier une interface, mais on doit l'implémenter. Java
introduit un mot clé interface permettant d'en définir une.
Exemple :
interface UneInterface {
int entier=1997;
void Fonction ();
}
A noter que bien que les données soient finales et que les méthodes soient abstraites, il n'est pas
nécessaire de le préciser explicitement puisque c'est sous-entendu.
Pour implémenter une interface en Java, on utilise le mot clé implements.
Exemple :
class ImplInter implements UneInterface {
void Fonction () {
// implémentation
}
}
Il est tout à fait possible d'hériter d'une classe tout en implémentant une interface :
class Classe extends Parent implements UneInterface {
// ...
}
Notez enfin qu'une interface peut être dérivée d'une autre interface, de la même façon qu'une
classe.
L'utilisation des interfaces permet généralement de combler l'absence d'héritage multiple (hériter
de plusieurs classes en même temps) en Java.
Nous finirons ce chapitre sur l'orienté objet en Java en présentant les packages.
11. Les packages
Nous avons dit plus haut qu'un fichier source Java devait impérativement porter le nom d'une
classe définie dans celui-ci. En fait, cette classe a une autre particularité : elle doit être la seule à
être déclarée comme étant publique (grâce au mot clé public). De plus, seule cette classe est visible
de l'extérieur (i.e. utilisable dans d'autres classes contenues dans d'autres fichiers sources).
Afin de permettre le regroupement de classes dédiées à un même thème, les concepteurs de Java on
adopté le principe du package. Un package est un ensemble de classes, définies dans un même
répertoire. Deux mot clés permettent de manipuler les packages.
On crée un package en Java grâce au mot clé package, suivi de son nom.
Exemple :
package UnPackage;
public class UneClasse {
// ...
}
Dans cet exemple, le fichier source UneClasse.java se trouve dans un répertoire portant le nom du
package, soit UnPackage.
Si ensuite on souhaite utiliser la classe de ce package, on met en tête de source le mot clé import :
import UnPackage.UneClasse;
//...
Dans cet exemple, le fichier source doit se trouver au même niveau que le répertoire UnPackage.
Le point entre le nom de la classe et le nom du package dans la commande import permet de
spécifier que la classe se trouve dans le répertoire UnPackage.
Lorsqu'un package comporte plusieurs classes, il devient fastidieux d'importer les classes une par
une. C'est pourquoi, on peut dire au compilateur que l'on désire importer toutes les classes d'un
même package en mettant une étoile à la place du nom de la classe.
Exemple :
import UnPackage.*;
C'est cette forme que l'on rencontre très souvent en Java, afin d'importer les classes de l'API livrée
avec le langage. Les principaux packages de l'API sont les suivants :
java.lang : le package de base, implicitement importé par le compilateur,
java.net : la gestion réseau,
java.awt : l'interface graphique AWT,
java.io : pour faire des entrées/sorties (accès fichiers, ...),
java.util : classes de base (chaînes, ...).
Il en existe une quinzaine d'autres, livrés avec le JDK.
Ce chapitre consacré à la POO en Java est terminé, nous allons maintenant présenter les principaux
packages constituant l'API Java.
Téléchargement