Fiche 2 :Introduction à la notion de paquetage en Java
L'objectif de cette fiche est de vous expliquer tout ce que vous avez toujours rêvé de savoir sur les paquetages, la compilation et l'exécution en Java, sans jamais oser le demander. Nous allons voir :
- comment structurer un programme à l'aide des paquetages ;
- comment compiler correctement un programme ;
- comment exécuter correctement un programme.
Remarque
Même si la plupart d'entre vous utilisent ou utiliseront à terme des environnements de développement intégrés (Eclipse, Netbeans, IntelliJ...), où la plupart des opérations de compilation et d'exécution sont automatisées, il est néanmoins indispensable de comprendre comment ça fonctionne, afin d'être capable de comprendre d'où viennent les problèmes lorsqu'il y en a.
Avant de commencer, vous pouvez relire la section de la fiche d'introduction qui présente la compilation et exécution dans un contexte très simple (en gros mono-répertoire, voire mono-fichier).
Les paquetages
Généralités et déclaration
En Java, on peut regrouper des classes en paquetages (packages), afin de mieux structurer l'application (ex : classes métiers, utilitaires, IHM,...). Cette structuration a trois intérêts :
- permettre la modularité, séparation plus claire des différentes parties de l'application ;
- éviter les conflits de noms (en fournissant un espace de nommage) ;
- limiter la visibilité des classes.
Pour déclarer qu'une classe fait partie d'un paquetage, on utilise
l'instruction package
en toute première instruction d'une classe.
Exemple :
package fr.ensimag.animaux;
class Pangolin { // La classe Pangolin fait donc partie du paquetage fr.ensimag.animaux
[...]
}
Remarques
Si l'on ne spécifie pas d'instruction package, la classe appartient par défaut au paquetage anonyme.
Selon le coding style, les noms des paquetages sont en minuscule (et souvent normalisés).
Le .
induit une hiérarchie de paquetages : le paquetage
fr.ensimag.animaux
est un sous-paquetage du paquetage
fr.ensimag
.
La hiérarchie des paquetages est directement liée aux répertoires
contenant les fichiers source. Le paquetage par défaut est dans le
répertoire racine .
, le paquetage fr.ensimag
est lui
dans le sous-répertoire ./fr/ensimag
, ...
Visibilité
L'utilisation des paquetages induit deux niveaux de visibilité pour les
classes : public
et par défaut. Si la classe est déclarée
public
, elle est visible et accessible depuis n'importe quel
paquetage. Si on ne met rien, elle ne sera accessible que depuis son
paquetage.
Utilisation
Dès qu'une classe fait partie d'un paquetage, elle change de nom et
porte en préfixe le nom du paquetage. Ainsi par exemple, notre classe
Pangolin ci-dessus s'appelle désormais
fr.ensimag.animaux.Pangolin
. Pour utiliser cette classe, il
faut donc :
- soit utiliser son nom complet (on dit aussi nom qualifié)
fr.ensimag.animaux.Pangolin
; - soit ajouter en début de fichier (après une éventuelle instruction
package
) une instruction d'importation de la classe :import fr.ensimag.animaux.Pangolin;
. Si cette ligne est présente au début du fichier, alors on pourra utiliser le nom court (Pangolin
) dans tout le fichier.
Remarque
L'instruction d'importation n'importe pas le code de la classe au sens propre. Cette commande signifie simplement littéralement que l'on a le droit d'utiliser le nom court de la classe importée en lieu et place du nom qualifié.
S'il y a un conflit de nom (une classe Pangolin
dans deux
paquetages différents), alors il faut impérativement utiliser le nom
long.
On peut également importer toutes les classes d'un paquetage en
utilisant le symbole *
. Ainsi, par exemple,
import fr.ensimag.animaux.*;
signifie simplement que toutes les
classes du paquetage fr.ensimag.animaux;
pourront être
utilisées avec leur nom court. Attention, cela ne s'applique pas
récursivement aux sous-paquetages (s'il existe par exemple des classes
dans un sous-paquetage fr.ensimag.animaux.gui
ces classes ne
seront pas importées).
Paquetages et compilation / exécution
Lorsque l'on compile une classe d'un certain paquetage, le compilateur
place automatiquement le code binaire (le fichier compilé) dans un sous
répertoire dont le nom correspond à celui du paquetage. Ainsi, une
classe Pangolin
appartenant au paquetage
fr.ensimag.animaux
sera compilée dans le fichier
fr/ensimag/animaux/Pangolin.class
.
On voit donc qu'il y a une correspondance entre la hiérarchie de paquetages et la hiérarchie du système de fichiers. Cela a une incidence sur la compilation et l'exécution.
Classpath et sourcepath
Pour bien comprendre comment le compilateur et la JVM trouvent les ressources nécessaires lors de la compilation et de l'exécution, il faut parler du classpath et du sourcepath.
- Le classpath est une variable contenant la liste des répertoires du système de fichiers dans lesquels le compilateur et la JVM peuvent aller chercher les bytecodes des classes dont ils peuvent avoir besoin.
- Le sourcepath est une variable contenant la liste des répertoires dans lesquels le compilateur peut aller chercher les codes sources des classes dont il peut avoir besoin.
On peut spécifier ces deux variables :
- soit en positionnant les variables d'environnement
CLASSPATH
etSOURCEPATH
. Exemple en bash :
export CLASSPATH=$CLASSPATH:/home/canard/java:./bin
- soit en passant directement le classpath et le sourcepath en
argument du compilateur ou de la JVM (avec
-classpath
et-sourcepath
).
Lorsque le compilateur trouve dans le classpath et dans le sourcepath deux versions de la même classe qui peuvent convenir, il prend la plus récente. S'il s'agit d'un fichier source, il recompile d'abord ce fichier pour créer le bytecode nécessaire.
Attention
Le compilateur comme la JVM s'attendent à trouver chaque classe nécessaire à la compilation et à l'exécution dans le sous-répertoire correspondant à son paquetage d'au moins un des répertoires du classpath ou du sourcepath (pour le compilateur).
Par exemple, si l'exécution de la classe TestPangolin
requiert
le bytecode de la classe fr.ensimag.animaux.Pangolin
situé dans
le répertoire /home/canard/java/fr/ensimag/animaux
, le
classpath devra être positionné sur /home/canard/java
, et non
sur /home/canard/java/fr/ensimag/animaux
.
Un exemple
Considérons l'exemple suivant où nous voulons compiler puis exécuter notre programme de pangolins. La structure du système de fichiers (à partir de l'endroit où nous nous trouvons) est la suivante :
- .
- src
- fr
- ensimag
- animaux
- Pangolin.java
- animaux
- ensimag
- TestPangolin.java
- fr
- bin
- src
Afin de compiler correctement, nous devons fixer le sourcepath de manière à ce que le compilateur trouve les bonnes classes. Voici ce que l'on peut taper :
javac -d ./bin -sourcepath ./src -classpath ./bin src/TestPangolin.java
Ici, l'option -d
permet de préciser dans quel répertoire
doivent être placées les classes compilées. L'option
-classpath ./bin
n'a aucun effet, mais permettrait de prendre
en compte des classes compilées plus récentes que le source si jamais il
y en avait dans le répertoire bin
.
Après cette ligne de commande, notre système de fichier se trouve dans la situation suivante :
- .
- src
- fr
- ensimag
- animaux
- Pangolin.java
- animaux
- ensimag
- TestPangolin.java
- fr
- bin
- fr
- ensimag
- animaux
- Pangolin.class
- animaux
- ensimag
- TestPangolin.class
- fr
- src
Le compilateur a créé lui-même la hiérarchie de répertoires dans le
répertoire bin
. Dans cette situation, pour lancer le programme,
il suffit de procéder comme suit :
java -classpath ./bin TestPangolin
Encore une fois, ce que l'on passe comme argument obligatoire à la JVM
est le nom d'une classe, pas le nom d'un fichier. D'où le fait qu'il
n'y ait pas d'extension à TestPangolin
, et qu'il ne soit pas
nécessaire de préciser que cette classe est dans le sous-répertoire
bin
(ce répertoire est dans le classpath).
Archives JAR
Pour distribuer du bytecode Java, il peut être utile de fournir une simple archive plutôt qu'un ensemble de fichiers avec une arborescence compliquée. L'écosystème Java possède un outil pour ça, les fichiers JAR (pour Java ARchive − jar signifie aussi en Anglais « bocal, pot », ce qui permettait aux créateurs de Java, qui sont des petits rigolos, de faire un bon jeu de mot au passage).
Un fichier JAR ressemble à s'y méprendre à une archive TAR. D'ailleurs, la syntaxe pour créer une archive JAR est quasiment la même. Jugez plutôt :
jar cvf jean-michel.jar
Une spécificité est que l'on peut créer des archives JAR exécutables. Pour cela, il faut :
- Créer un fichier
manifest.txt
avec le contenu suivant :Main_Class: TestPangolin
(à supposer que votre classe contenant la méthodemain
soit la classeTestPangolin
). - Créer le JAR en ajoutant le fichier manifest, de la manière suivante :
Une fois une telle archive créée, vous pourrez lancer la JVM sur
jean-michel.jar
avec l'option -jar
:
java -jar jean-michel.jar
Pour les adeptes des cliquodromes, la plupart des environnements de bureaux savent lancer une archive JAR exécutable lorsque l'on clique dessus.
Remarque
Une autre spécificité des fichiers JAR est que l'on peut les utiliser dans un classpath. Lorsqu'il y a des fichiers JAR dans un classpath, le compilateur ou la JVM va regarder ce qu'ils contiennent en les considérant de la même manière que s'ils étaient des répertoires. Ainsi, lorsque vous récupérez une bibliothèque sous forme d'archive JAR, vous pouvez en utiliser les classes sans même avoir à la décompresser, en incluant directement la bibliothèque dans le classpath.
Ci-dessus : Jean-Michel Jarre vous remercie d'avoir lu cette fiche. [Source: Zero Coool CC BY-SA 2.0 https://commons.wikimedia.org/wiki/File:Jean_Michel_Jarre_-_Roland_AX-Synth.jpg]