Javaでのパッケージと静的インポート

私の前ではJavaの101チュートリアル、あなたはより良い他の参照型とブロックのメンバーとして(また、クラスとインタフェースとして知られている)参照型を宣言することで、あなたのコードを整理する方法を学びました。また、ネストを使用して、ネストされた参照型と同じ名前を共有するトップレベルの参照型の間で名前の競合を回避する方法も示しました。

ネストに加えて、Javaはパッケージを使用して、トップレベルの参照型の同名の問題を解決します。静的インポートを使用すると、パッケージ化されたトップレベルの参照型の静的メンバーへのアクセスも簡素化されます。静的インポートを使用すると、コード内のこれらのメンバーにアクセスするときにキーストロークを節約できますが、それらを使用するときに注意すべき点がいくつかあります。このチュートリアルでは、Javaプログラムでパッケージと静的インポートを使用する方法を紹介します。

ダウンロードコードを取得するこのJavaチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

パッケージ参照型

Java開発者は、関連するクラスとインターフェースをパッケージにグループ化します。パッケージを使用すると、参照型を見つけて使用しやすくなり、同じ名前の型間の名前の競合を回避し、型へのアクセスを制御できます。

このセクションでは、パッケージについて学習します。パッケージとは何かを調べ、packageandimportステートメントについて学び、保護されたアクセス、JARファイル、および型検索の追加トピックを調べます。

Javaのパッケージとは何ですか?

ソフトウェア開発では、通常、階層関係に従ってアイテムを整理します。たとえば、前のチュートリアルでは、クラスを他のクラスのメンバーとして宣言する方法を示しました。ファイルシステムを使用して、ディレクトリを他のディレクトリにネストすることもできます。

これらの階層構造を使用すると、名前の競合を回避するのに役立ちます。たとえば、非階層ファイルシステム(単一のディレクトリ)では、複数のファイルに同じ名前を割り当てることはできません。対照的に、階層ファイルシステムでは、同じ名前のファイルを異なるディレクトリに存在させることができます。同様に、2つの囲んでいるクラスには、同じ名前のネストされたクラスを含めることができます。アイテムは異なる名前空間に分割されているため、名前の競合は存在しません。

Javaでは、トップレベル(ネストされていない)参照型を複数の名前空間に分割できるため、これらの型をより適切に整理し、名前の競合を防ぐことができます。 Javaでは、パッケージ言語機能を使用して、トップレベルの参照型を複数の名前空間に分割します。この場合、パッケージは参照型を格納するための一意の名前空間です。パッケージには、クラスとインターフェイス、および他のパッケージ内にネストされたパッケージであるサブパッケージを格納できます。

パッケージには名前があり、予約されていない識別子である必要があります。たとえば、java。メンバーアクセス演算子(.)は、パッケージ名をサブパッケージ名から分離し、パッケージまたはサブパッケージ名をタイプ名から分離します。たとえば、サブパッケージ名とは別のパッケージ名、およびタイプ名とは別のサブjava.lang.Systemパッケージ名の2メンバーアクセス演算子。javalanglangSystem

参照型はpublic、パッケージの外部からアクセスできるように宣言する必要があります。同じことが、アクセス可能でなければならない定数、コンストラクター、メソッド、またはネストされた型にも当てはまります。これらの例は、チュートリアルの後半で確認できます。

パッケージステートメント

Javaでは、packageステートメントを使用してパッケージを作成します。このステートメントはソースファイルの上部に表示され、ソースファイルタイプが属するパッケージを識別します。次の構文に準拠している必要があります。

 package identifier[.identifier]*; 

パッケージステートメントは、予約語で始まりpackage、識別子で続きます。その後、オプションで、ピリオドで区切られた識別子のシーケンスが続きます。セミコロン(;)はこのステートメントを終了します。

最初の(左端の)識別子はパッケージに名前を付け、後続の各識別子はサブパッケージに名前を付けます。たとえば、ではpackage a.b;、ソースファイルで宣言されているすべてのタイプbは、aパッケージのサブパッケージに属しています。

パッケージ/サブパッケージの命名規則

慣例により、パッケージ名またはサブパッケージ名は小文字で表します。名前が複数の単語で構成されている場合は、最初の単語を除く各単語を大文字にすることをお勧めします。たとえば、generalLedger

コンパイルの問題を回避するために、パッケージ名のシーケンスは一意である必要があります。たとえば、2つの異なるgraphicsパッケージを作成し、各graphicsパッケージにTriangle異なるインターフェイスを持つクラスが含まれていると仮定します。Javaコンパイラが以下のようなものに遭遇した場合、Triangle(int, int, int, int)コンストラクタが存在することを確認する必要があります。

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

三角形の境界ボックス

Triangleコンストラクターは、三角形を描画する境界ボックスを指定するものと考えてください。最初の2つのパラメーターはボックスの左上隅を識別し、次の2つのパラメーターはボックスの範囲を定義します。

コンパイラgraphicsは、Triangleクラスを含むパッケージが見つかるまで、アクセス可能なすべてのパッケージを検索します。見つかったパッケージにコンストラクターをTriangle持つ適切なクラスが含まれている場合Triangle(int, int, int, int)、すべてが正常です。それ以外の場合、見つかったTriangleクラスにTriangle(int, int, int, int)コンストラクターがない場合、コンパイラーはエラーを報告します。(検索アルゴリズムについては、このチュートリアルの後半で詳しく説明します。)

このシナリオは、一意のパッケージ名シーケンスを選択することの重要性を示しています。一意の名前シーケンスを選択する際の規則は、インターネットドメイン名を逆にして、シーケンスのプレフィックスとして使用することです。たとえば、ドメイン名であるca.javajeffため、プレフィックスとして選択しますjavajeff.ca。次に、ca.javajeff.graphics.Triangleにアクセスするように指定しますTriangle

ドメイン名コンポーネントと有効なパッケージ名

ドメイン名コンポーネントは、常に有効なパッケージ名であるとは限りません。1つ以上のコンポーネント名は、数字(3D.com)で始まるか、ハイフン(-)または別の不正な文字(ab-z.com)を含むか、Javaの予約語(short.com)の1つである可能性があります。慣例では、数字の前にアンダースコア(com._3D)を付け、不正な文字をアンダースコア()に置き換えcom.ab_z、予約語のサフィックスにアンダースコア()を付ける必要がありますcom.short_

パッケージステートメントに関する追加の問題を回避するには、いくつかのルールに従う必要があります。

  1. ソースファイルで宣言できるパッケージステートメントは1つだけです。
  2. パッケージステートメントの前にコメント以外のものを付けることはできません。

2番目のルールの特殊なケースである最初のルールは、参照型を複数のパッケージに格納する意味がないために存在します。パッケージは複数のタイプを格納できますが、タイプは1つのパッケージにのみ属することができます。

ソースファイルがパッケージステートメントを宣言していない場合、ソースファイルのタイプは名前のないパッケージに属していると言われます。重要な参照型は通常、独自のパッケージに格納され、名前のないパッケージを避けます。

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

複数の開発者のプロジェクトで作業する場合は、*ワイルドカードの使用を避けて、他の開発者がソースコードで使用されているタイプを簡単に確認できるようにします。