Packages et importations statiques en Java

Dans mon précédent didacticiel Java 101 , vous avez appris à mieux organiser votre code en déclarant des types de référence (également appelés classes et interfaces) en tant que membres d'autres types et blocs de référence. Je vous ai également montré comment utiliser l'imbrication pour éviter les conflits de noms entre les types de référence imbriqués et les types de référence de niveau supérieur partageant le même nom.

Parallèlement à l'imbrication, Java utilise des packages pour résoudre les problèmes de même nom dans les types de référence de niveau supérieur. L'utilisation d'importations statiques simplifie également l'accès aux membres statiques dans les types de référence de niveau supérieur empaquetés. Les importations statiques vous feront économiser des frappes lors de l'accès à ces membres dans votre code, mais il y a quelques points à surveiller lorsque vous les utilisez. Dans ce tutoriel, je vais vous présenter l'utilisation des packages et des importations statiques dans vos programmes Java.

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel Java. Créé par Jeff Friesen pour JavaWorld.

Types de référence d'emballage

Les développeurs Java regroupent les classes et interfaces associées dans des packages. L'utilisation de packages facilite la localisation et l'utilisation des types de référence, évite les conflits de noms entre les types de même nom et contrôle l'accès aux types.

Dans cette section, vous découvrirez les packages. Vous découvrirez quels sont les paquets, informez - vous sur les packageet importdéclarations, et d' explorer les sujets supplémentaires d'accès protégé, les fichiers JAR et les recherches de type.

Que sont les packages en Java?

Dans le développement de logiciels, nous organisons généralement les éléments en fonction de leurs relations hiérarchiques. Par exemple, dans le didacticiel précédent, je vous ai montré comment déclarer des classes comme membres d'autres classes. Nous pouvons également utiliser des systèmes de fichiers pour imbriquer des répertoires dans d'autres répertoires.

L'utilisation de ces structures hiérarchiques vous aidera à éviter les conflits de noms. Par exemple, dans un système de fichiers non hiérarchique (un seul répertoire), il n'est pas possible d'attribuer le même nom à plusieurs fichiers. En revanche, un système de fichiers hiérarchique permet à des fichiers portant le même nom d'exister dans différents répertoires. De même, deux classes englobantes peuvent contenir des classes imbriquées de même nom. Les conflits de noms n'existent pas car les éléments sont partitionnés en différents espaces de noms.

Java nous permet également de partitionner les types de référence de niveau supérieur (non imbriqués) en plusieurs espaces de noms afin que nous puissions mieux organiser ces types et éviter les conflits de noms. En Java, nous utilisons la fonctionnalité de langage de package pour partitionner les types de référence de niveau supérieur en plusieurs espaces de noms. Dans ce cas, un package est un espace de noms unique pour stocker les types de référence. Les packages peuvent stocker des classes et des interfaces, ainsi que des sous- packages, qui sont des packages imbriqués dans d'autres packages.

Un package a un nom, qui doit être un identifiant non réservé; par exemple java,. L'opérateur d'accès aux membres ( .) sépare un nom de package d'un nom de sous-package et sépare un nom de package ou de sous-package d'un nom de type. Par exemple, les opérateurs d'accès à deux membres dans java.lang.Systemun nom javade package distinct du nom de sous- langpackage et séparent le nom langde sous- package du nom de Systemtype.

Les types de référence doivent être déclarés publicaccessibles depuis l'extérieur de leurs packages. La même chose s'applique à toutes les constantes, constructeurs, méthodes ou types imbriqués qui doivent être accessibles. Vous en verrez des exemples plus loin dans le didacticiel.

La déclaration du package

En Java, nous utilisons l' instruction package pour créer un package. Cette instruction apparaît en haut d'un fichier source et identifie le package auquel appartiennent les types de fichiers source. Il doit être conforme à la syntaxe suivante:

 package identifier[.identifier]*; 

Une instruction de package commence par le mot réservé packageet se poursuit avec un identificateur, qui est éventuellement suivi d'une séquence d'identificateurs séparés par un point. Un point-virgule ( ;) termine cette instruction.

Le premier identifiant (le plus à gauche) nomme le package, et chaque identifiant suivant nomme un sous-package. Par exemple, dans package a.b;, tous les types déclarés dans le fichier source appartiennent au bsous- apackage du package.

Convention de dénomination des packages / sous-packages

Par convention, nous exprimons un nom de package ou de sous-package en minuscules. Lorsque le nom se compose de plusieurs mots, vous souhaiterez peut-être mettre chaque mot en majuscule, à l'exception du premier; par exemple generalLedger,.

Une séquence de noms de packages doit être unique pour éviter les problèmes de compilation. Par exemple, supposons que vous créez deux graphicspackages différents et supposez que chaque graphicspackage contient une Triangleclasse avec une interface différente. Lorsque le compilateur Java rencontre quelque chose comme ce qui est ci-dessous, il doit vérifier que le Triangle(int, int, int, int)constructeur existe:

 Triangle t = new Triangle(1, 20, 30, 40); 

Boîte englobante triangulaire

Pensez au Triangleconstructeur comme spécifiant une boîte englobante dans laquelle dessiner le triangle. Les deux premiers paramètres identifient le coin supérieur gauche de la boîte, et les deux seconds paramètres définissent l'étendue de la boîte.

Le compilateur recherchera tous les packages accessibles jusqu'à ce qu'il trouve un graphicspackage contenant une Triangleclasse. Si le package trouvé inclut la Triangleclasse appropriée avec un Triangle(int, int, int, int)constructeur, tout va bien. Sinon, si la Triangleclasse trouvée n'a pas de Triangle(int, int, int, int)constructeur, le compilateur signale une erreur. (J'en dirai plus sur l'algorithme de recherche plus loin dans ce tutoriel.)

Ce scénario illustre l'importance de choisir des séquences de nom de package uniques. La convention de sélection d'une séquence de noms unique consiste à inverser votre nom de domaine Internet et à l'utiliser comme préfixe pour la séquence. Par exemple, je choisirais ca.javajeffcomme préfixe car javajeff.cac'est mon nom de domaine. Je spécifierais alors ca.javajeff.graphics.Triangled'accéder Triangle.

Composants de nom de domaine et noms de package valides

Les composants de nom de domaine ne sont pas toujours des noms de package valides. Un ou plusieurs noms de composant peuvent commencer par un chiffre ( 3D.com), contenir un trait d'union ( -) ou un autre caractère non autorisé ( ab-z.com), ou être l'un des mots réservés de Java ( short.com). La convention veut que vous préfixiez le chiffre avec un trait de soulignement ( com._3D), remplacez le caractère illégal par un trait de soulignement ( com.ab_z) et suffixez le mot réservé avec un trait de soulignement ( com.short_).

Vous devez suivre quelques règles pour éviter des problèmes supplémentaires avec l'instruction package:

  1. Vous ne pouvez déclarer qu'une seule instruction de package dans un fichier source.
  2. Vous ne pouvez pas faire précéder l'instruction de package avec autre chose que des commentaires.

La première règle, qui est un cas particulier de la deuxième règle, existe car il n'a pas de sens de stocker un type de référence dans plusieurs packages. Bien qu'un package puisse stocker plusieurs types, un type ne peut appartenir qu'à un seul package.

Lorsqu'un fichier source ne déclare pas d'instruction de package, les types du fichier source sont censés appartenir au package sans nom . Les types de référence non triviaux sont généralement stockés dans leurs propres packages et évitent le package sans nom.

Java implementations map package and subpackage names to same-named directories. For example, an implementation would map graphics to a directory named graphics. In the case of the package a.b, the first letter, a would map to a directory named a and b would map to a b subdirectory of a. The compiler stores the class files that implement the package's types in the corresponding directory. Note that the unnamed package corresponds to the current directory.

Example: Packaging an audio library in Java

A practical example is helpful for fully grasping the package statement. In this section I demonstrate packages in the context of an audio library that lets you read audio files and obtain audio data. For brevity, I'll only present a skeletal version of the library.

The audio library currently consists of only two classes: Audio and WavReader. Audio describes an audio clip and is the library's main class. Listing 1 presents its source code.

Listing 1. Package statement example (Audio.java)

 package ca.javajeff.audio; public final class Audio { private int[] samples; private int sampleRate; Audio(int[] samples, int sampleRate) { this.samples = samples; this.sampleRate = sampleRate; } public int[] getSamples() { return samples; } public int getSampleRate() { return sampleRate; } public static Audio newAudio(String filename) { if (filename.toLowerCase().endsWith(".wav")) return WavReader.read(filename); else return null; // unsupported format } } 

Let's go through Listing 1 step by step.

  • The Audio.java file in Listing 1 stores the Audio class. This listing begins with a package statement that identifies ca.javajeff.audio as the class's package.
  • Audio is declared public so that it can be referenced from outside of its package. Also, it's declared final so that it cannot be extended (meaning, subclassed).
  • Audio declares privatesamples and sampleRate fields to store audio data. These fields are initialized to the values passed to Audio's constructor.
  • Audio's constructor is declared package-private (meaning, the constructor isn't declared public, private, or protected) so that this class cannot be instantiated from outside of its package.
  • Audio presents getSamples() and getSampleRate() methods for returning an audio clip's samples and sample rate. Each method is declared public so that it can be called from outside of Audio's package.
  • Audio concludes with a public and staticnewAudio() factory method for returning an Audio object corresponding to the filename argument. If the audio clip cannot be obtained, null is returned.
  • newAudio() compares filename's extension with .wav (this example only supports WAV audio). If they match, it executes return WavReader.read(filename) to return an Audio object with WAV-based audio data.

Listing 2 describes WavReader.

Listing 2. The WavReader helper class (WavReader.java)

 package ca.javajeff.audio; final class WavReader { static Audio read(String filename) { // Read the contents of filename's file and process it // into an array of sample values and a sample rate // value. If the file cannot be read, return null. For // brevity (and because I've yet to discuss Java's // file I/O APIs), I present only skeletal code that // always returns an Audio object with default values. return new Audio(new int[0], 0); } } 

WavReader is intended to read a WAV file's contents into an Audio object. (The class will eventually be larger with additional private fields and methods.) Notice that this class isn't declared public, which makes WavReader accessible to Audio but not to code outside of the ca.javajeff.audio package. Think of WavReader as a helper class whose only reason for existence is to serve Audio.

Complete the following steps to build this library:

  1. Select a suitable location in your file system as the current directory.
  2. Create a ca/javajeff/audio subdirectory hierarchy within the current directory.
  3. Copy Listings 1 and 2 to files Audio.java and WavReader.java, respectively; and store these files in the audio subdirectory.
  4. Assuming that the current directory contains the ca subdirectory, execute javac ca/javajeff/audio/*.java to compile the two source files in ca/javajeff/audio. If all goes well, you should discover Audio.class and WavReader.class files in the audio subdirectory. (Alternatively, for this example, you could switch to the audio subdirectory and execute javac *.java.)

Now that you've created the audio library, you'll want to use it. Soon, we'll look at a small Java application that demonstrates this library. First, you need to learn about the import statement.

Java's import statement

Imagine having to specify ca.javajeff.graphics.Triangle for each occurrence of Triangle in source code, repeatedly. Java provides the import statement as a convenient alternative for omitting lengthy package details.

The import statement imports types from a package by telling the compiler where to look for unqualified (no package prefix) type names during compilation. It appears near the top of a source file and must conform to the following syntax:

 import identifier[.identifier]*.(typeName | *); 

An import statement starts with reserved word import and continues with an identifier, which is optionally followed by a period-separated sequence of identifiers. A type name or asterisk (*) follows, and a semicolon terminates this statement.

The syntax reveals two forms of the import statement. First, you can import a single type name, which is identified via typeName. Second, you can import all types, which is identified via the asterisk.

The * symbol is a wildcard that represents all unqualified type names. It tells the compiler to look for such names in the right-most package of the import statement's package sequence unless the type name is found in a previously searched package. Note that using the wildcard doesn't have a performance penalty or lead to code bloat. However, it can lead to name conflicts, which you will see.

For example, import ca.javajeff.graphics.Triangle; tells the compiler that an unqualified Triangle class exists in the ca.javajeff.graphics package. Similarly, something like

 import ca.javajeff.graphics.*; 

tells the compiler to look in this package when it encounters a Triangle name, a Circle name, or even an Account name (if Account has not already been found).

Avoid the * in multi-developer projects

Lorsque vous travaillez sur un projet multi-développeur, évitez d'utiliser le *caractère générique afin que les autres développeurs puissent facilement voir quels types sont utilisés dans votre code source.