Les paquetages et les chemins de classe

Chapitres traités   

Les paquetages se révèlent pratiques pour l'organisation de votre travail et pour effectuer une séparation entre nos créations et les bibliothèques fournies par des tiers.

La définition d'un chemin de classes permet à la machine virtuelle de s'y retrouver dans l'ensemble des classes stockées dans différents répertoires ou sous-répertoires du système de fichiers et de lancer ainsi correctement l'application avec les différentes classes requises.

 

Choix du chapitre Les paquetages

Java permet de regrouper des classes dans un ensemble (ou un paquet) appelé paquetage (package). Les paquetages se révèlent pratiques pour l'organisation de votre travail et pour effectuer une séparation entre nos créations et les bibliothèques fournies par des tiers.

Bibliothèque standard

La bibliothèque standard de Java est distribuée dans un certain nombre de paquetages : java.lang, java.util, java.net, etc. Les paquetages standard de Java constituent des exemples de paquetages hiérarchiques.

Tout comme les répertoires d'un disque dur, les paquetages peuvent être organisés suivant plusieurs niveaux d'imbrication. Tous les packetages standard de Java se trouvent au sein des hiérachies de paquetage java et javax.

Intérêt des paquetages

L'utilisation des paquetages permet de s'assurer que le nom de chaque classe est unique. Supposons que deux programmeurs aient la brillante idée de fournir une classe Employé. Tant qu'ils placent leurs classes dans des paquetages différents, il n'y a pas de conflit.

En fait, pour s'assurer vraiment que le nom d'un paquetage est unique, Sun recommande d'utiliser comme préfixe le nom du domaine Internet de votre société (à priori unique), écrit dans l'ordre inverse de ses éléments. Vous utilisez ensuite des sous-paquetages pour les différents projets.

Par exemple, manu.e3b.org est le nom de domaine utilisé pour enregistrer ces cours. Inversé, il devient le paquetage org.e3b.manu ou si vous désirez qu'il soit plus cours : org.manu. Ce paquetage peut encore être subdivisé en sous-paquetages tels que org.manu.cheminsclasse.

Du point de vue du compilateur, il n'y a absolument aucune relation entre les paquetages imbriqués. Par exemple, les paquetages java.util et java.util.jar n'ont rien à voir l'un avec l'autre. Chacun représente sa propre collection indépendante de classes.

Importation des classes

Une classe peut utiliser toutes les classes de son propre paquetage et toutes les classes publiques des autres paquetages. Lorsque une classe doit atteindre une classe de son propre paquetage, vous n'avez aucune notation particulière à rajouter. Lorsque, par contre, vous avez besoin d'atteindre une classe publique d'un autre paquetage, il faut alors, d'une manière ou d'une autre, spécifier ce paquetage particulier.

Vous pouvez accéder aux classes publiques d'un autre paquetage de deux façons :

  1. La première consiste simplement à ajouter le nom complet du paquetage devant chaque nom de classe :

    java.util.Date aujourdhui = new java.util.Date();

    Cette technique est plutôt contraignante puisque vous devez donner cette précision supplémentaire en préfixe de la classe à chaque fois que vous en avez besoin. Si vous l'utiliser une dizaine de fois, vous devrez rajouter ce préfixe dix fois, ce qui peut être fastidieux à la longue et surtout, rend le code moins lisible puisque beaucoup plus imposant.

  2. Il est plus simple d'avoir recours à l'importation qui s'applique à l'aide de la directive import. Cette directive est un raccourci permettant de faire référence aux classes du paquetage. Une fois que cette directive import est spécifiée, il n'est plus nécessaire de donner aux classes leur nom complet.

    Vous pouvez importer une classe spécifique ou l'ensemble d'un paquetage. Vous placez les instructions import en tête de vos fichiers sources (mais au-dessous de toutes instructions package).

    Par exemple, vous pouvez importer toutes les classes du paquetage java.util avec l'instruction suivante :

    import java.util.*;

    Vous pouvez alors écrire l'instruction suivante sans le préfixe de paquetage :

    Date aujourdhui = new Date();

    Il est également possible d'importer une classe spécifique d'un paquetage :

    import java.util.Date;
    ...
    Date aujourdhui = new Date();

    La syntaxe java.util.* est moins compliquée. Cela n'entraîne aucun effet négatif sur la taille du code. Par contre, si vous importez explicitement des classes, le lecteur de votre code connaît exactement les classes utilisées.

    Sachez toutefois que vous ne pouvez utiliser la notation * que pour importer un seul paquetage. Vous ne pouvez pas utiliser import java.* ni import java.*.* pour importer tous les paquetages ayant la préfixe java.

Gérer les conflits de noms

Le plus souvent, vous importez simplement les paquetages dont vous avez besoin, sans autre préoccupation. La seule circonstance demandant une attention particulière est le conflit de noms. Par exemple, à la fois le paquetage java.util et java.sql ont une classe Date.

  1. Supposons que vous écriviez un programme qui importe les deux paquetages :

    import java.util.*;
    import java.sql.*;

  2. Si vous utilisez maintenant la classe Date, vous obtiendrez une erreur de compilation :

    Date aujourdhui = new Date(); // Erreur -- java.util.Date ou java.sql.Date ?

    Le compilateur ne peut déterminer de quelle Date vous avez besoin.
    §

  3. Ce problème peut être résolu par l'ajout d'une instruction import spécifique :

    import java.util.*;
    import java.sql.*;
    import java.util.Date;

  4. Mais, si vous avez besoin des deux classes Date ? Vous devez alors utiliser le nom complet du paquetage avec chaque nom de classe :

    java.util.Date hier = new java.util.Date();
    java.sql.Date aujourdhui = new java.sql.Date(...);

La localisation des classes dans les paquetages est le rôle du compilateur. Les bytecodes dans les fichiers de classe utilisent toujours les noms complets des paquetages pour faire référence aux autres classes.

 

Importations statiques

Depuis la version 5.0 de Java, l'instruction import a été améliorée de manière à permettre l'importation de méthodes et de champs statiques, et non plus simplement des classes.

Si vous ajoutez la directive d'importation statique en haut de votre fichier source, vous pouvez utiliser les méthodes et les champs statiques de la classe invoquée, cette fois-ci sans le préfixe du nom de la classe :

import static java.lang.System.*;
...
out.println("Bonjour à tout le monde"); // c'est-à-dire System.out
...
exit(0); // c'est-à-dire System.exit

Vous pouvez également importer une méthode ou un champ spécifique :

import static java.lang.System.out;

Dans la pratique, il semble douteux que de nombreux programmeurs souhaitent abréger System.out ou System.exit. Le résultat me paraît moins clair. Mais il existe deux utilisations pratiques des imports statiques :

  1. Les fonctions mathématiques : si vous utilisez un import statique pour la classe Math, vous pouvez utiliser des fonctions mathématiques d'une manière plus naturelle :

    import static java.lang.Math.*;
    ...
    sqrt(pow(x, 2) + pow(y, 2)); // plus facile à lire et à comprendre que
    Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));

  2. Des constantes encombrantes : Si vous utilisez de nombreuses constantes avec des noms compliqués, l'import statique vous ravira :

    import static java.lang.Calendar.*;
    ...
    if (aujourdhui.get(DAY_OF_WEEK) == MONDAY) // plus facile à interpréter que
    if (aujourdhui.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) ...

Ajout d'une classe dans un paquetage

Pour placer des classes dans un paquetage, vous devez mettre tout simplement le nom de votre paquetage, à la suite du mot réservé package, en haut de votre fichier source, avant le code qui définit les classes dans le paquetage.

Par exemple, le fichier Employé.java doit commencer ainsi :

package org.manu.personnels;

public class Employé {
...
}

Si vous ne mettez pas une instruction package dans le fichier source, les classes dans ce fichier source appartiendront au paquetage par défaut qui n'a pas de nom de paquetage.

  1. Vous devez placez vos fichiers source d'un paquetage dans un sous-répertoire correspondant au nom complet du paquetage. Par exemple, tous les fichiers source dans le paquetage org.manu.personnels doivent se trouver dans le sous-répertoire org/manu/personnels. Le compilateur place ensuite les fichiers de classe dans la même structure de répertoire.
  2. Pour compiler un tel fichier, vous devez impérativement vous placer dans le répertoire de base et non pas au niveau du fichier source. Par exemple, voici comment compiler le fichier source précédent :

    javac org/manu/personnels/Employé.java

  3. De la même façon, vous devez vous placer dans le répertoire de base pour exécuter le programme correspondant, issu du fichier Employé.class :

    java org.manu.personnels.Employé

    N'oubliez pas que le compilateur fonctionne sur les fichiers (avec les séparateurs de fichiers et une extension .java), tandis que l'interpréteur Java charge une classe (avec des séparateurs par point).

Visibilité dans un paquetage

Nous avons déjà rencontré les modificateurs d'accès public et private. Les composants logiciels déclarés public sont utilisable par n'importe quel classe. Les éléments private ne sont accessible que dans la classe qui les définit. Si vous ne spécifiez pas de modificateur public ou private, un composant (classe, méthode ou attribut) est accessible à toutes les méthodes du même paquetage.

 

Choix du chapitre Le chemin de classe

Nous venons de voir que les classes sont stockées dans des sous-répertoires du système de fichiers. Le chemin d'accès à la classe (classpath) doit correspondre au nom du paquetage. Vous pouvez aussi employer l'utilitaire JAR pour ajouter des fichiers de classe à une archive. Une archive contient plusieurs fichiers de classe et des sous-répertoires dans un fichier compressé, ce qui économise l'espace et réduit le temps d'accès.

Pour en connaître plus sur les archives.
§

Lorsque vous utilisez une bibliothèque tierce dans vos programmes, vous recevrez généralement un ou plusieurs fichiers JAR à inclure. Le JDK propose également plusieurs fichiers JAR, comme jre/lib/rt.jar, qui contient des milliers de classes de bibliothèques.

Pour rendre vos classes accessibles aux programmes, vous devez :

  1. Placer vos classes à l'intérieur d'un répertoire, disons /home/user/répertoireclasses. Notez que ce répertoire est celui de base pour l'arborescence de paquetage. Si vous ajoutez la classe org.manu.personnels.Employé, le fichier Employé.class doit être localisé dans le sous-répertoire /home/user/répertoireclasses/org/manu/personnels.
  2. Placer les fichiers JAR dans un répertoire, par exemple /home/user/archives.
  3. Définir le chemin de classe (classpath). Ce chemin est la collection de tous les emplacements pouvant contenir des fichiers de classe.

Localisation suivant les systèmes d'exploitation

  1. Sous Unix, les éléments dans le chemin de classe sont séparés par des caractères deux-points :

    /home/user/répertoireclasses : . : /home/user/archives/archive.jar

  2. Sous Windows, ils sont séparés par des points-vigules :

    C:\répertoireclasses ; . ; C:\archives\archive.jar

Dans les deux cas, le point désigne le répertoire courant.
§

Contenu d'un chemin de classe

Ce chemin de classe contient :

  1. le répertoire de base /home/user/répertoireclasses ou C:\répertoireclasses ;
  2. le répertoire courant (.) ;
  3. le fichier JAR /home/user/archives/archives.jar ou C:\archives\archive.jar.
Depuis Java SE 6, vous pouvez utiliser un caractère générique pour un répertoire de fichier JAR, comme :

/home/user/répertoireclasses : . : /home/user/archives/' * '

ou :

C:\répertoireclasses ; . ; C:\archives\*

Sous Unix, le * doit être échappé pour éviter l'expansion du shell.
§

Tous les fichiers JAR (à l'exception des fichiers .class) du répertoire archives figurent dans ce chemin de classes.
§

Les classes recherchent toujours les fichiers de bibliothèque d'exécution (rt.jar et les autres fichiers JAR dans les répertoires jre/lib et jre/lib/ext, mais pas les fichiers .class) ; vous les incluez explicitement dans le chemin de classe.

Attention : Le compilateur javac recherche toujours les fichiers dans le répertoire courant, mais l'interpréteur java n'examine le répertoire courant que si le répertoire " . " fait partie du chemin d'accès de classe. En l'absence de définition de chemin de classe, cela ne pose pas de problème, le chemin de classe par défaut est le répertoire " . ". Si vous avez défini le chemin de classe et oublié l'écriture du répertoire " . ", vos programmes seront compilés sans erreur, mais ils ne pourront pas s'exécuter.

Fonctionnement de la machine virtuelle et du compilateur par rapport au chemin de classe

Le chemin de classe recense tous les répertoires et les fichiers archives constituant des points de départ pour retrouver les classes :

/home/user/répertoireclasses : . : /home/user/archives/archive.jar

Machine virtuelle : Supposons que la machine vituelle Java recherche le fichier de la classe org.manu.personnels.Employé. Elle va d'abord chercher dans les fichiers de classe système qui sont stockés dans les répertoires jre/lib et jre/lib/ext. Elle n'y trouvera pas le fichier de classes et va donc se tourner vers le chemin de classe et rechercher les fichiers suivants :
  1. /home/user/répertoireclasses/org/manu/personnels/Employé.class ;
  2. org/manu/personnels/Employé.class en commençant par le répertoire courant ;
  3. org/manu/personnels/Employé.class dans /home/user/archives/archive.jar.
Compilateur : La tâche du compilateur est plus difficile que celle de la machine virtuelle en ce qui concerne la localisation de fichiers. Si vous faites référence à une classe sans spécifier son paquetage, le compilateur doit d'abord trouver le paquetage qui contient la classe. Il consulte alors toutes les directives import en tant que sources possibles pour la classe.

Supposons par exemple que le fichier source contienne les directives si dessous et que le code source fasse référence à une classe Employé :

import java.util.*;
import org.manu.personnels.*;

Le compilateur essaiera alors de trouver java.lang.Employé (car le paquetage java.lang est toujours importé par défaut), java.util.Employé, org.manu.personnels.Employé et Employé dans le paquetage courant.

Il recherche chacune de ces classes dans tous les emplacements du chemin de classe. Une erreur de compilation se produit si plus d'une classe est trouvée (les classes devant être uniques, l'ordre des instructions import n'a pas d'importance.

L'étape suivante pour le compilateur consiste à consulter les fichiers source pour voir si la source est plus récente que le fichier de classe. Si oui, le fichier source est recompilé automatiquement.

Souvenez-vous que vous ne pouvez qu'importer des classes publiques des autres paquetages. Un fichier source peut seulement contenir une classe publique, et les noms du fichier et de la classe publique doivent correspondre.

Le compilateur peut donc facilement localiser les fichiers source pour les classes publiques. Vous pouvez importer des classes non publiques à partir des paquetages courants. Ces classes peuvent être définies dans des fichiers source avec des noms différents. Si vous importez une classe à partir d'un paquetage courant, le compilateur examine tous les fichiers sources de ce paquetage pour vérifier celui qui la définit.

Définition du chemin de classe

Mieux vaut spécifier le chemin de classe avec l'option -classpath (ou -cp) :

  1. Sous Unix :

    java -classpath /home/user/répertoireclasses : . : /home/user/archives/archive.jar MonProgramme.java

  2. Sous Windows :

    java -classpath C:répertoireclasses ; . ; C:/archives/archive.jar MonProgramme.java

    L'ensemble de la commande doit être tapé sur une seule ligne. Mieux vaut également placer cette longue ligne dans un script de shell ou un fichier de lot.

La méthode préférée pour définir le chemin de classe consiste à employer l'option -classpath. Vous pouvez aussi définir la variable d'environnement CLASSPATH, selon votre shell. Utilisez la commande :

  1. Avec le Bourne Again (bash) :

    export CLASSPATH = /home/user/répertoireclasses : . : /home/user/archives/archive.jar

  2. Avec le shell C :

    setenv CLASSPATH /home/user/répertoireclasses : . : /home/user/archives/archive.jar

  3. Avec le shell Windows :

    set CLASSPATH = C:répertoireclasses ; . ; C:/archives/archive.jar

    Le chemin de classe est défini tant que le shell existe.
    §