Pakete und statische Importe in Java
In meinem vorherigen Java 101- Lernprogramm haben Sie gelernt, wie Sie Ihren Code besser organisieren können, indem Sie Referenztypen (auch als Klassen und Schnittstellen bezeichnet) als Mitglieder anderer Referenztypen und -blöcke deklarieren. Ich habe Ihnen auch gezeigt, wie Sie die Verschachtelung verwenden, um Namenskonflikte zwischen verschachtelten Referenztypen und Referenztypen der obersten Ebene mit demselben Namen zu vermeiden.
Neben der Verschachtelung verwendet Java Pakete, um gleichnamige Probleme in Referenztypen der obersten Ebene zu beheben. Die Verwendung statischer Importe vereinfacht auch den Zugriff auf die statischen Elemente in gepackten Referenztypen der obersten Ebene. Durch statische Importe sparen Sie Tastenanschläge, wenn Sie auf diese Elemente in Ihrem Code zugreifen. Bei der Verwendung sind jedoch einige Dinge zu beachten. In diesem Tutorial werde ich Ihnen die Verwendung von Paketen und statischen Importen in Ihren Java-Programmen vorstellen.
download Code herunterladen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Java-Tutorial. Erstellt von Jeff Friesen für JavaWorld.Verpackungsreferenztypen
Java-Entwickler gruppieren verwandte Klassen und Schnittstellen in Paketen. Die Verwendung von Paketen erleichtert das Auffinden und Verwenden von Referenztypen, das Vermeiden von Namenskonflikten zwischen gleichnamigen Typen und das Steuern des Zugriffs auf Typen.
In diesem Abschnitt erfahren Sie mehr über Pakete. Sie finden heraus , welche Pakete sind, erfahren Sie über die package
und import
Aussagen, und erkunden Sie die weiteren Themen der geschützten Zugang, JAR - Dateien und geben sucht.
Was sind Pakete in Java?
In der Softwareentwicklung organisieren wir Elemente üblicherweise nach ihren hierarchischen Beziehungen. Im vorherigen Tutorial habe ich Ihnen beispielsweise gezeigt, wie Sie Klassen als Mitglieder anderer Klassen deklarieren. Wir können auch Dateisysteme verwenden, um Verzeichnisse in anderen Verzeichnissen zu verschachteln.
Durch die Verwendung dieser hierarchischen Strukturen können Sie Namenskonflikte vermeiden. In einem nicht hierarchischen Dateisystem (einem einzelnen Verzeichnis) ist es beispielsweise nicht möglich, mehreren Dateien denselben Namen zuzuweisen. Im Gegensatz dazu können in einem hierarchischen Dateisystem gleichnamige Dateien in verschiedenen Verzeichnissen vorhanden sein. In ähnlicher Weise können zwei umschließende Klassen gleichnamige verschachtelte Klassen enthalten. Namenskonflikte bestehen nicht, da Elemente in verschiedene Namespaces unterteilt sind.
Mit Java können wir auch Referenztypen der obersten Ebene (nicht verschachtelt) in mehrere Namespaces unterteilen, um diese Typen besser zu organisieren und Namenskonflikte zu vermeiden. In Java verwenden wir die Paketsprachenfunktion, um Referenztypen der obersten Ebene in mehrere Namespaces zu partitionieren. In diesem Fall ist ein Paket ein eindeutiger Namespace zum Speichern von Referenztypen. Pakete können Klassen und Schnittstellen sowie Unterpakete speichern, bei denen es sich um Pakete handelt, die in anderen Paketen verschachtelt sind.
Ein Paket hat einen Namen, der eine nicht reservierte Kennung sein muss. zum Beispiel java
. Der Member Access Operator ( .
) trennt einen Paketnamen von einem Unterpaketnamen und einen Paket- oder Unterpaketnamen von einem Typnamen. Zum Beispiel kann die zwei Mitglieder Zugang Betreiber in java.lang.System
getrennten Paketnamen java
aus dem lang
subpackage Namen und separatem subpackage Namen lang
aus dem System
Typnamen.
Referenztypen müssen deklariert werden public
, um von außerhalb ihrer Pakete zugänglich zu sein. Gleiches gilt für alle Konstanten, Konstruktoren, Methoden oder verschachtelten Typen, auf die zugegriffen werden muss. Beispiele hierfür finden Sie später im Tutorial.
Die Paketanweisung
In Java verwenden wir die package-Anweisung , um ein Paket zu erstellen. Diese Anweisung wird oben in einer Quelldatei angezeigt und gibt das Paket an, zu dem die Quelldateitypen gehören. Es muss der folgenden Syntax entsprechen:
package identifier[.identifier]*;
Eine Paketanweisung beginnt mit dem reservierten Wort package
und setzt sich mit einem Bezeichner fort, dem optional eine durch Punkte getrennte Folge von Bezeichnern folgt. Ein Semikolon ( ;
) beendet diese Anweisung.
Der erste (ganz links stehende) Bezeichner benennt das Paket, und jeder nachfolgende Bezeichner benennt ein Unterpaket. Beispielsweise gehören in package a.b;
alle in der Quelldatei deklarierten Typen zum b
Unterpaket des a
Pakets.
Namenskonvention für Pakete / Unterpakete
Konventionell drücken wir einen Paket- oder Unterpaketnamen in Kleinbuchstaben aus. Wenn der Name aus mehreren Wörtern besteht, möchten Sie möglicherweise jedes Wort mit Ausnahme des ersten groß schreiben. zum Beispiel generalLedger
.
Eine Folge von Paketnamen muss eindeutig sein, um Kompilierungsprobleme zu vermeiden. Angenommen, Sie erstellen zwei verschiedene graphics
Pakete und nehmen an, dass jedes graphics
Paket eine Triangle
Klasse mit einer anderen Schnittstelle enthält. Wenn der Java-Compiler auf etwas wie das Folgende stößt, muss er überprüfen, ob der Triangle(int, int, int, int)
Konstruktor vorhanden ist:
Triangle t = new Triangle(1, 20, 30, 40);
Dreieck-Begrenzungsrahmen
Stellen Sie sich den Triangle
Konstruktor als einen Begrenzungsrahmen vor, in dem das Dreieck gezeichnet werden soll. Die ersten beiden Parameter kennzeichnen die obere linke Ecke der Box, und die zweiten beiden Parameter definieren die Ausdehnung der Box.
Der Compiler durchsucht alle zugänglichen Pakete, bis er ein graphics
Paket findet, das eine Triangle
Klasse enthält . Wenn das gefundene Paket die entsprechende Triangle
Klasse mit einem Triangle(int, int, int, int)
Konstruktor enthält, ist alles in Ordnung. Andernfalls meldet der Compiler einen Fehler , wenn die gefundene Triangle
Klasse keinen Triangle(int, int, int, int)
Konstruktor hat. (Ich werde später in diesem Tutorial mehr über den Suchalgorithmus sagen.)
Dieses Szenario zeigt, wie wichtig es ist, eindeutige Paketnamenfolgen auszuwählen. Die Konvention bei der Auswahl einer eindeutigen Namenssequenz besteht darin, Ihren Internetdomänennamen umzukehren und ihn als Präfix für die Sequenz zu verwenden. Zum Beispiel würde ich ca.javajeff
als Präfix wählen, weil javajeff.ca
mein Domainname ist. Ich würde dann angeben, ca.javajeff.graphics.Triangle
auf zuzugreifen Triangle
.
Domänennamenkomponenten und gültige Paketnamen
Domänennamenkomponenten sind nicht immer gültige Paketnamen. Ein oder mehrere Komponentennamen beginnen möglicherweise mit einer Ziffer ( 3D.com
), enthalten einen Bindestrich ( -
) oder ein anderes unzulässiges Zeichen ( ab-z.com
) oder sind eines der reservierten Wörter von Java ( short.com
). Die Konvention schreibt vor, dass Sie der Ziffer einen Unterstrich ( com._3D
) voranstellen , das unzulässige Zeichen durch einen Unterstrich ( com.ab_z
) ersetzen und das reservierte Wort durch einen Unterstrich ( com.short_
) ergänzen .
Sie müssen einige Regeln befolgen, um zusätzliche Probleme mit der Paketanweisung zu vermeiden:
- Sie können nur eine Paketanweisung in einer Quelldatei deklarieren.
- Sie können der Paketanweisung nur Kommentare voranstellen.
Die erste Regel, die ein Sonderfall der zweiten Regel ist, existiert, weil es nicht sinnvoll ist, einen Referenztyp in mehreren Paketen zu speichern. Obwohl ein Paket mehrere Typen speichern kann, kann ein Typ nur zu einem Paket gehören.
Wenn eine Quelldatei keine Paketanweisung deklariert, gehören die Typen der Quelldatei zum unbenannten Paket . Nicht triviale Referenztypen werden normalerweise in ihren eigenen Paketen gespeichert und vermeiden das unbenannte Paket.
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
Vermeiden Sie bei der Arbeit an einem Projekt mit mehreren Entwicklern die Verwendung des *
Platzhalters, damit andere Entwickler leicht erkennen können, welche Typen in Ihrem Quellcode verwendet werden.