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 :

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 :

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 lui-mê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 :

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 :

  1. une classe heritage permettant d'instancier des objets des deux autres classes. Elle contient une méthode main(),
  2. une classe Forme comprenant une donnée membre, deux constructeurs et une méthode identification(),
  3. 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 :

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.