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 packageund importAussagen, 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.Systemgetrennten Paketnamen javaaus dem langsubpackage Namen und separatem subpackage Namen langaus dem SystemTypnamen.

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 packageund 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 bUnterpaket des aPakets.

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 graphicsPakete und nehmen an, dass jedes graphicsPaket eine TriangleKlasse 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 TriangleKonstruktor 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 graphicsPaket findet, das eine TriangleKlasse enthält . Wenn das gefundene Paket die entsprechende TriangleKlasse mit einem Triangle(int, int, int, int)Konstruktor enthält, ist alles in Ordnung. Andernfalls meldet der Compiler einen Fehler , wenn die gefundene TriangleKlasse 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.javajeffals Präfix wählen, weil javajeff.camein Domainname ist. Ich würde dann angeben, ca.javajeff.graphics.Triangleauf 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:

  1. Sie können nur eine Paketanweisung in einer Quelldatei deklarieren.
  2. 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 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

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.