En mi tutorial anterior de Java 101 , aprendió cómo organizar mejor su código declarando tipos de referencia (también conocidos como clases e interfaces) como miembros de otros tipos y bloques de referencia. También le mostré cómo usar el anidamiento para evitar conflictos de nombres entre tipos de referencia anidados y tipos de referencia de nivel superior que comparten el mismo nombre.
Junto con el anidamiento, Java usa paquetes para resolver problemas con el mismo nombre en tipos de referencia de nivel superior. El uso de importaciones estáticas también simplifica el acceso a los miembros estáticos en tipos de referencia de nivel superior empaquetados. Las importaciones estáticas le ahorrarán pulsaciones de teclas al acceder a estos miembros en su código, pero hay algunas cosas a tener en cuenta cuando los usa. En este tutorial, le presentaré el uso de paquetes e importaciones estáticas en sus programas Java.
descargar Obtener el código Descargar el código fuente, por ejemplo, aplicaciones en este tutorial de Java. Creado por Jeff Friesen para JavaWorld.Tipos de referencia de empaque
Los desarrolladores de Java agrupan clases e interfaces relacionadas en paquetes. El uso de paquetes facilita la localización y el uso de tipos de referencia, evita conflictos de nombres entre tipos del mismo nombre y controla el acceso a los tipos.
En esta sección, aprenderá sobre los paquetes. Usted se dará cuenta qué paquetes son, aprender sobre el package
y import
declaraciones, y explorar los temas adicionales de acceso protegido, archivos JAR y búsquedas tipo.
¿Qué son los paquetes en Java?
En el desarrollo de software, normalmente organizamos elementos de acuerdo con sus relaciones jerárquicas. Por ejemplo, en el tutorial anterior, le mostré cómo declarar clases como miembros de otras clases. También podemos usar sistemas de archivos para anidar directorios en otros directorios.
El uso de estas estructuras jerárquicas le ayudará a evitar conflictos de nombres. Por ejemplo, en un sistema de archivos no jerárquico (un solo directorio), no es posible asignar el mismo nombre a varios archivos. Por el contrario, un sistema de archivos jerárquico permite que los archivos con el mismo nombre existan en diferentes directorios. De manera similar, dos clases adjuntas pueden contener clases anidadas del mismo nombre. Los conflictos de nombres no existen porque los elementos están divididos en diferentes espacios de nombres.
Java también nos permite dividir tipos de referencia de nivel superior (no anidados) en múltiples espacios de nombres para que podamos organizar mejor estos tipos y evitar conflictos de nombres. En Java, utilizamos la función de lenguaje de paquetes para dividir tipos de referencia de nivel superior en varios espacios de nombres. En este caso, un paquete es un espacio de nombres exclusivo para almacenar tipos de referencia. Los paquetes pueden almacenar clases e interfaces, así como subpaquetes, que son paquetes anidados dentro de otros paquetes.
Un paquete tiene un nombre, que debe ser un identificador no reservado; por ejemplo java
,. El operador de acceso de miembros ( .
) separa un nombre de paquete de un nombre de subpaquete y separa un nombre de paquete o subpaquete de un nombre de tipo. Por ejemplo, los operadores de acceso de dos miembros en el java.lang.System
nombre java
del paquete separado del nombre del lang
subpaquete y el nombre lang
del subpaquete separado del nombre del System
tipo.
Los tipos de referencia deben declararse public
accesibles desde fuera de sus paquetes. Lo mismo se aplica a las constantes, constructores, métodos o tipos anidados que deben ser accesibles. Verá ejemplos de estos más adelante en el tutorial.
La declaración del paquete
En Java, usamos la declaración del paquete para crear un paquete. Esta declaración aparece en la parte superior de un archivo fuente e identifica el paquete al que pertenecen los tipos de archivos fuente. Debe ajustarse a la siguiente sintaxis:
package identifier[.identifier]*;
Una declaración de paquete comienza con la palabra reservada package
y continúa con un identificador, que es seguido opcionalmente por una secuencia de identificadores separados por puntos. Un punto y coma ( ;
) termina esta declaración.
El primer identificador (el más a la izquierda) nombra el paquete y cada identificador subsiguiente nombra un subpaquete. Por ejemplo, en package a.b;
, todos los tipos declarados en el archivo fuente pertenecen al b
subpaquete del a
paquete.
Convención de nomenclatura de paquetes / subpaquetes
Por convención, expresamos el nombre de un paquete o subpaquete en minúsculas. Cuando el nombre consta de varias palabras, es posible que desee poner en mayúscula cada palabra excepto la primera; por ejemplo generalLedger
,.
Una secuencia de nombres de paquetes debe ser única para evitar problemas de compilación. Por ejemplo, suponga que crea dos graphics
paquetes diferentes y asume que cada graphics
paquete contiene una Triangle
clase con una interfaz diferente. Cuando el compilador de Java encuentra algo como lo que se muestra a continuación, debe verificar que el Triangle(int, int, int, int)
constructor existe:
Triangle t = new Triangle(1, 20, 30, 40);
Cuadro delimitador triangular
Piense en el Triangle
constructor como si especificara un cuadro delimitador en el que dibujar el triángulo. Los primeros dos parámetros identifican la esquina superior izquierda del cuadro y los dos segundos parámetros definen la extensión del cuadro.
El compilador buscará todos los paquetes accesibles hasta que encuentre un graphics
paquete que contenga una Triangle
clase. Si el paquete encontrado incluye la Triangle
clase apropiada con un Triangle(int, int, int, int)
constructor, todo está bien. De lo contrario, si la Triangle
clase encontrada no tiene un Triangle(int, int, int, int)
constructor, el compilador informa de un error. (Diré más sobre el algoritmo de búsqueda más adelante en este tutorial).
Este escenario ilustra la importancia de elegir secuencias únicas de nombres de paquetes. La convención al seleccionar una secuencia de nombre única es invertir su nombre de dominio de Internet y usarlo como prefijo para la secuencia. Por ejemplo, elegiría ca.javajeff
como prefijo porque javajeff.ca
es mi nombre de dominio. Luego, especificaría el ca.javajeff.graphics.Triangle
acceso Triangle
.
Componentes de nombre de dominio y nombres de paquetes válidos
Los componentes del nombre de dominio no siempre son nombres de paquete válidos. Uno o más nombres de componentes pueden comenzar con un dígito ( 3D.com
), contener un guión ( -
) u otro carácter ilegal ( ab-z.com
), o ser una de las palabras reservadas de Java ( short.com
). La convención dicta que debe anteponer el dígito con un guión bajo ( com._3D
), reemplazar el carácter ilegal con un guión bajo ( com.ab_z
) y agregar un sufijo a la palabra reservada con un guión bajo ( com.short_
).
Debe seguir un par de reglas para evitar problemas adicionales con la declaración del paquete:
- Puede declarar solo una declaración de paquete en un archivo fuente.
- No puede preceder la declaración del paquete con nada más que comentarios.
La primera regla, que es un caso especial de la segunda regla, existe porque no tiene sentido almacenar un tipo de referencia en varios paquetes. Aunque un paquete puede almacenar varios tipos, un tipo puede pertenecer a un solo paquete.
Cuando un archivo fuente no declara una declaración de paquete, se dice que los tipos del archivo fuente pertenecen al paquete sin nombre . Los tipos de referencia no triviales generalmente se almacenan en sus propios paquetes y evitan el paquete sin nombre.
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 theAudio
class. This listing begins with a package statement that identifiesca.javajeff.audio
as the class's package. Audio
is declaredpublic
so that it can be referenced from outside of its package. Also, it's declaredfinal
so that it cannot be extended (meaning, subclassed).Audio
declaresprivate
samples
andsampleRate
fields to store audio data. These fields are initialized to the values passed toAudio
's constructor.Audio
's constructor is declared package-private (meaning, the constructor isn't declaredpublic
,private
, orprotected
) so that this class cannot be instantiated from outside of its package.Audio
presentsgetSamples()
andgetSampleRate()
methods for returning an audio clip's samples and sample rate. Each method is declaredpublic
so that it can be called from outside ofAudio
's package.Audio
concludes with apublic
andstatic
newAudio()
factory method for returning anAudio
object corresponding to thefilename
argument. If the audio clip cannot be obtained,null
is returned.newAudio()
comparesfilename
's extension with.wav
(this example only supports WAV audio). If they match, it executesreturn WavReader.read(filename)
to return anAudio
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:
- Select a suitable location in your file system as the current directory.
- Create a
ca/javajeff/audio
subdirectory hierarchy within the current directory. - Copy Listings 1 and 2 to files
Audio.java
andWavReader.java
, respectively; and store these files in theaudio
subdirectory. - Assuming that the current directory contains the
ca
subdirectory, executejavac ca/javajeff/audio/*.java
to compile the two source files inca/javajeff/audio
. If all goes well, you should discoverAudio.class
andWavReader.class
files in theaudio
subdirectory. (Alternatively, for this example, you could switch to theaudio
subdirectory and executejavac *.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
Cuando trabaje en un proyecto de varios desarrolladores, evite utilizar el *
comodín para que otros desarrolladores puedan ver fácilmente qué tipos se utilizan en su código fuente.