Java 101: les packages organisent les classes et les interfaces

Pourquoi réinventer la roue? Ce cliché s'applique au développement de logiciels où certains développeurs réécrivent fréquemment le même code pour différents programmes. Deux inconvénients de cette approche sont:

  1. Ça fait perdre du temps
  2. Il introduit le potentiel de bogues dans le code débogué

Au lieu de réécrire le même code, de nombreux environnements de développement de logiciels fournissent un outil de bibliothèque qui organise le code fréquemment utilisé. Une fois que les développeurs ont terminé de déboguer du code réutilisable, ils utilisent l'outil pour stocker ce code dans une bibliothèque - un ou plusieurs fichiers contenant du code fréquemment utilisé à utiliser dans divers programmes. Pendant la création du programme, le compilateur ou l'outil de bibliothèque accède à la bibliothèque pour connecter le code référencé à la bibliothèque du programme au programme.

Les bibliothèques sont fondamentales pour Java. Ils permettent, en partie, au chargeur de classe de la JVM de localiser les fichiers de classe. (J'explorerai les chargeurs de classe dans un prochain article.) Pour cette raison, les bibliothèques Java sont communément appelées bibliothèques de classes. Cependant, Java fait référence aux bibliothèques de classes en tant que packages.

Cet article explore les packages; Je vous montre comment créer des packages de classes et d'interfaces, comment importer (c'est-à-dire importer dans un programme) des classes et interfaces packagées, comment déplacer des packages sur le disque dur et comment utiliser des fichiers jar pour encapsuler des packages.

Remarque
L'expérience de package unique de cet article est spécifique à Microsoft Windows. Vous devriez pouvoir facilement extrapoler cette expérience à des plates-formes non Windows.

Que sont les packages?

Un package est une collection de classes et d'interfaces. Chaque package a son propre nom et organise ses classes et interfaces de niveau supérieur (c'est-à-dire imbriquées) dans un espace de noms distinct ou une collection de noms. Bien que les classes et interfaces de même nom ne puissent pas apparaître dans le même package, elles peuvent apparaître dans différents packages car un espace de noms distinct est attribué à chaque package.

Du point de vue de l'implémentation, assimiler un package à un répertoire s'avère utile, tout comme assimiler les classes et les interfaces d'un package aux fichiers de classe d'un répertoire. Gardez à l'esprit les autres approches, telles que l'utilisation de bases de données, pour implémenter des packages, alors ne prenez pas l'habitude de toujours assimiler les packages à des répertoires. Mais comme de nombreuses machines virtuelles Java utilisent des répertoires pour implémenter des packages, cet article assimile les packages à des répertoires. Le SDK Java 2 organise sa vaste collection de classes et d'interfaces dans une hiérarchie arborescente de packages au sein de packages, ce qui équivaut à des répertoires dans des répertoires. Cette hiérarchie permet à Sun Microsystems de distribuer facilement (et de travailler facilement avec) ces classes et interfaces. Des exemples de packages Java incluent:

  • java.lang:Une collection de classes liées au langage, telles que Objectet String, organisées dans le sous- javapackage du langpackage
  • java.lang.ref:Une collection de classes de langage liées à la référence, telles que SoftReferenceet ReferenceQueue, organisées dans le refsous-sous-package du sous- javapackage du langpackage
  • javax.swing:Une collection de classes de composants liées à Swing, telles que JButton, et d'interfaces, telles que ButtonModel, organisées dans le sous- javaxpackage du swingpackage

Les caractères de période séparent les noms de package. Par exemple, dans javax.swing, un caractère point sépare le nom javaxdu package du nom du sous-package swing. Un caractère point est l'équivalent indépendant de la plate-forme des /barres obliques ( ), des barres obliques inversées ( \) ou d'autres caractères pour séparer les noms de répertoire dans une implémentation de package basée sur un répertoire, les branches de base de données dans une implémentation de package basée sur une base de données hiérarchique, etc. .

Pointe
Tout comme vous ne pouvez pas stocker à la fois un fichier et un répertoire avec des noms identiques dans le même répertoire, vous ne pouvez pas stocker une classe ou une interface et un package avec des noms identiques dans le même package. Par exemple, étant donné un package nommé accounts, vous ne pouvez pas stocker à la fois un package et une classe nommés payabledans accounts. Pour éviter les conflits de noms, mettez en majuscule la première lettre des noms de classe et d'interface et en minuscule la première lettre des noms de package. En utilisant l'exemple précédent, stockez la classe Payabledans le package en accountstant que accounts.Payableet le package payabledans le package en accountstant que accounts.payable. Apprenez-en davantage à ce sujet et sur d'autres conventions de dénomination dans les conventions de code de Sun pour le langage de programmation Java .

Créer un package de classes et d'interfaces

Les classes et interfaces de chaque fichier source s'organisent dans un package. En l' packageabsence de la directive, ces classes et interfaces appartiennent au package sans nom (le répertoire que la JVM considère comme le répertoire courant — le répertoire où un programme Java commence son exécution via le programme Windows java.exe, ou équivalent au système d'exploitation — et ne contient aucun sous-package) . Mais si la packagedirective apparaît dans un fichier source, cette directive nomme le package pour ces classes et interfaces. Utilisez la syntaxe suivante pour spécifier une packagedirective dans le code source:

'package' packageName ['.' subpackageName ...] ';'

Une packagedirective commence par le packagemot - clé. Un identifiant dont les noms d' un paquet, packageName, suit immédiatement. Si des classes et des interfaces doivent apparaître dans un sous-paquet (à un certain niveau) à l'intérieur packageName, un ou plusieurs subpackageNameidentificateurs séparés par des points apparaissent après packageName. Le fragment de code suivant présente une paire de packagedirectives:

jeu de paquet; package game.devices;

La première packagedirective identifie un package nommé game. Toutes les classes et interfaces apparaissant dans le fichier source de cette directive sont organisées dans le gamepackage. La deuxième packagedirective identifie un sous-paquet nommé devices, qui réside dans un paquet nommé game. Toutes les classes et interfaces apparaissant dans le fichier source de cette directive sont organisées dans le sous- gamepaquetage du devicespackage. Si une implémentation JVM mappe des noms de package à des noms de répertoire, game.devicescorrespond à une game\deviceshiérarchie de répertoires sous Windows et une game/deviceshiérarchie de répertoires sous Linux ou Solaris.

Mise en garde
Une seule packagedirective peut apparaître dans un fichier source. De plus, la packagedirective doit être le premier code (à l'exception des commentaires) de ce fichier. La violation de l'une ou l'autre de ces règles entraîne le signalement d'une erreur par le compilateur Java.

Pour vous aider à vous familiariser avec les packages, j'ai préparé un exemple qui couvre tous les sujets de cet article. Dans cette section, vous apprendrez à créer le package de l'exemple. Dans les sections suivantes, vous apprendrez comment importer une classe et une interface à partir de ce package, comment déplacer ce package vers un autre emplacement sur votre disque dur tout en continuant d'accéder au package à partir d'un programme, et comment stocker le package dans un fichier jar . Le listing 1 présente le code source du paquet:

Listing 1. A.java

// Paquet A.java testpkg; classe publique A {int x = 1; public int y = 2; protégé int z = 3; int returnx () {retour x; } public int returny () {return y; } protected int returnz () {return z; } interface publique StartStop {void start (); arrêt vide (); }} classe B {public static void hello () {System.out.println ("hello"); }}

Le listing 1 présente le code source de votre premier package nommé. La package testpkg;directive nomme ce package testpkg. À l'intérieur se testpkgtrouvent des classes Aet B. Il Ay a trois déclarations de champ, trois déclarations de méthode et une déclaration d'interface interne. À l'intérieur se Btrouve une déclaration de méthode unique. Le code source entier est stocké dans A.javacar Aest une classe publique. Notre tâche: transformer ce code source en un package composé de deux classes et d'une interface interne (ou un répertoire contenant trois fichiers de classe). Les étapes spécifiques à Windows suivantes accomplissent cette tâche:

  1. Open a Windows command window and ensure you are in the c: drive's root directory (the main directory—represented by an initial backslash (\) character). To do that, type the c: command followed by the cd \ command. (If you use a different drive, replace c: with your chosen drive. Also, do not forget to press the Enter key after typing a command.)
  2. Create a testpkg directory by typing md testpkg. Note: When following this article's steps, do not type periods after the commands.
  3. Make testpkg the current directory by typing cd testpkg.
  4. Use an editor to enter Listing 1's source code and save that code to an A.java file in testpkg.
  5. Compile A.java by typing javac A.java. You should see classfiles A$StartStop.class, A.class, and B.class appear in the testpkg directory.

Figure 1 illustrates Steps 3 through 5.

Congratulations! You have just created your first package. Think of this package as containing two classes (A and B) and A's single inner interface (StartStop). You can also think of this package as a directory containing three classfiles: A$StartStop.class, A.class, and B.class.

Note
To minimize package name conflicts (especially among commercial packages), Sun has established a convention in which a company's Internet domain name reverses and prefixes a package name. For example, a company with x.com as its Internet domain name and a.b as a package name (a) followed by a subpackage name (b) prefixes com.x to a.b, resulting in com.x.a.b. My article does not follow this convention because the testpkg package is a throw-away designed for teaching purposes only.

Import a package's classes and interfaces

Once you have a package, you will want to import classes and/or interfaces—actually, class and/or interface names—from that package to your program, so it can use those classes and/or interfaces. One way to accomplish that task is to supply the fully qualified package name (the package name and all subpackage names) in each place where the reference type name (the class or interface name) appears, as Listing 2 demonstrates:

Listing 2. Usetestpkg1.java

// Usetestpkg1.java class Usetestpkg1 implements testpkg.A.StartStop { public static void main (String [] args) { testpkg.A a = new testpkg.A (); System.out.println (a.y); System.out.println (a.returny ()); Usetestpkg1 utp = new Usetestpkg1 (); utp.start (); utp.stop (); } public void start () { System.out.println ("Start"); } public void stop () { System.out.println ("Stop"); } } 

By prefixing testpkg. to A, Usetestpkg1 accesses testpkg's class A in two places and A's inner interface StartStop in one place. Complete the following steps to compile and run Usetestpkg1:

  1. Open a Windows command window and make sure you are in the c: drive's root directory.
  2. Ensure the classpath environment variable does not exist by executing set classpath=. (I discuss classpath later in this article.)
  3. Use an editor to enter Listing 2's source code and save that code to a Usetestpkg1.java file in the root directory.
  4. Compile Usetestpkg1.java by typing javac Usetestpkg1.java. You should see classfile Usetestpkg1.class appear in the root directory.
  5. Type java Usetestpkg1 to run this program.

Figure 2 illustrates Steps 3 through 5 and shows the program's output.

According to Usetestpkg1's output, the main() method's thread successfully accesses testpkg.A's y field and calls the returny() method. Furthermore, the output shows a successful implementation of the testpkg.A.StartStop inner interface.

For Usetestpkg1, prefixing testpkg. to A in three places doesn't seem a big deal. But who wants to specify a fully qualified package name prefix in a hundred places? Fortunately, Java supplies the import directive to import a package's public reference type name(s), so you do not have to enter fully qualified package name prefixes. Express an import directive in source code via the following syntax:

'import' packageName [ '.' subpackageName ... ] '.' ( referencetypeName | '*' ) ';' 

An import directive consists of the import keyword immediately followed by an identifier that names a package, packageName. An optional list of subpackageName identifiers follows to identify the appropriate subpackage (if necessary). The directive concludes with either a referencetypeName identifier that identifies a specific class or interface from the package, or an asterisk (*) character. If referencetypeName appears, the directive is a single-type import directive. If an asterisk character appears, the directive is a type-on-demand import directive.

Caution
As with the package directive, import directives must appear before any other code, with three exceptions: a package directive, other import directives, or comments.

The single-type import directive imports the name of a single public reference type from a package, as the following code fragment demonstrates:

import java.util.Date; 

The previous single-type import directive imports class name Date into source code. As a result, you specify Date instead of java.util.Date in each place that class name appears in source code. For example, when creating a Date object, specify Date d = new Date (); instead of java.util.Date d = new java.util.Date ();.

Soyez prudent avec les importdirectives de type unique . Si le compilateur détecte une importdirective de type unique qui spécifie un nom de type de référence également déclaré dans un fichier source, le compilateur signale une erreur, comme le montre le fragment de code suivant:

import java.util.Date; Date de la classe {}

Le compilateur considère le fragment de code comme une tentative d'introduire deux types de référence avec le même Datenom: