プログラムを作っていると、以前作った関数と同じ処理が必要になる場合があります。いちばんてっとり早い方法はソースファイルからその関数をコピーすることですが、賢明な方法とはいえません。このような場合、自分で作成した関数をライブラリとしてまとめておくと便利です。ライブラリの作成で問題になるのが「名前の衝突」です。複数のライブラリを使うときに、同じ名前の関数や変数が存在すると、そのライブラリは正常に動作しないでしょう。この問題は「パッケージ (package)」を使うと解決することができます。
Java のパッケージはディレクトリで管理します。ディレクトリを作成して、そこに class ファイルを格納すれば、それをパッケージとして使用することができます。このとき、ディレクトリ名がパッケージ名になります。ディレクトリ名と class ファイル名は異なっていてもかまいません。ディレクトリの中に複数の class ファイルがある場合、それらをまとめたものが一つのパッケージになります。
簡単な例を示しましょう。次のリストを見てください。
リスト : foo\Foo.java package foo; public class Foo { public static int a = 10; public static void test() { System.out.println("package foo"); } }
リスト : bar\Bar.java package bar; public class Bar { public static int a = 20; public static void test() { System.out.println("package bar"); } }
カレントディレクトリにディレクトリ foo を作成し、その中にファイル Foo.java を格納します。同様に、ディレクトリ bar を作成して、ファイル Bar.java を格納します。ファイルの先頭には package 文でパッケージ名を記述します。外部に公開 (export) するクラスは public で宣言します。このとき、ファイル名は public 宣言したクラスの名前と同じにしてください。つまり、ファイル内で定義できる public なクラスは一つだけになります。
パッケージ内に定義されているクラスやメソッドなどの名前は、次の方法でアクセスすることができます。
パッケージ名.名前
パッケージ名が長いとプログラムを記述するのが大変です。パッケージ名を省略して名前だけでアクセスできると便利ですね。このために用意されている構文が import です。
2 は「オンデマンドのインポート宣言」と呼ばれる方法で、そのパッケージからプログラムで必要となる名前をインポートします。import では、2 の方法よりも個別に名前を書く 1 の方法が推奨されているようです。
環境変数 CLASSPATH を設定していない場合、もしくは CLASSPARH にカレントディレクトリを追加している場合、クラス Foo と Bar は次のように指定します。
import foo.Foo; import bar.Bar;
Java はクラスを探すとき、CLASSPATH が設定されている場合は、そのディレクトリから探します。CLASSPATH が設定されていなければ、カレントディレクトリを探します。見つからない場合は Java の標準ライブラリから探します。CLASSPATH を設定すると、カレントディレクトリを探索しないので、CLASSPATH にカレントディレクトリを追加するか、コマンド javac のオプション -classpath でカレントディレクトリ ( . ) を指定してください。
それでは実行してみましょう。
リスト : パッケージの使用例 (sample160.java) import foo.Foo; import bar.Bar; public class sample160 { public static void main(String[] args) { Foo.test(); System.out.println(Foo.a); Bar.test(); System.out.println(Bar.a); } }
$ javac foo/Foo.java $ ls foo Foo.class Foo.java $ javac bar/Bar.java $ ls bar Bar.class Bar.java $ javac sample160.java $ java sample160 package foo 10 package bar 20
最初に import で foo.Foo と bar.Bar をインポートします。これでパッケージに含まれる Foo, Bar にアクセスすることができます。Foo.test() を実行すれば、package foo と表示され、Bar.a の値を表示すると 20 になります。このように、パッケージを使うことで名前の衝突を回避することができます。
foo と bar を一つのディレクトリに格納することもできます。次の図を見てください。
./ baz/ foo/ Foo.java bar/ Bar.java
ディレクトリ baz の中にディレクトリ foo と bar があり、その中にソースファイル Foo.java と Bar.java があります。この場合、Foo.java と Bar.java の package 文は次のように宣言します。
package baz.foo; package baz.bar;
sample160.java の import 文は次のように指定します。
import baz.foo.Foo; import baz.bar.Bar;
package 文と import 文のパス区切り記号にはドット ( . ) を使います。これで今までと同様にクラス Foo, Bar を使用することができます。
ところで、パッケージに必要なのは class ファイルで、ソースファイルは別のディレクトリにあってもかまいません。たとえば、カレントディレクトリにソースファイル Foo.java, Bar.java, sample160.java があり、Foo.class と Bar.class を package 文で指定したディレクトリに配置する場合、javac のオプション -d を使うと便利です。
-d ディレクトリ
-d オプションはクラスファイルの出力先ディレクトリを設定します。javac は必要に応じてディレクトリを作成し、パッケージ名に対応したサブディレクトリに class ファイルを配置します。たとえば、Foo.java, Bar.java をコンパイルするとき -d . を指定すると、カレントディレクトリにサブディレクトリ baz を作成し、そのサブディレクトリに foo と bar を作成し、そこに class ファイルを配置します。
それでは実際に試してみましょう。
$ ls Bar.java Foo.java sample160.java $ javac -d . Foo.java $ ls Bar.java Foo.java baz sample160.java $ ls baz foo $ ls baz/foo Foo.class $ javac -d . Bar.java $ ls baz bar foo $ ls baz/bar Bar.class $ javac sample160.java $ java sample160 package foo 10 package bar 20
Java はコマンド jar を使って、パッケージ内にある複数の class ファイルを一つのファイルにまとめて取り扱うことができます。このファイルを Java Archive とか JAR ファイルといいます。
jar で JAR ファイルを作成するときは次のオプションを指定します。
jar cf ファイル名.jar パッケージ
jar のオプションは UNIX 系 OS のコマンド tar と同じです。オプションに v を付けるとメッセージが表示されます。たとえば、パッケージ baz を JAR ファイルにまとめると次のようになります。
$ jar cvf baz.jar baz マニフェストが追加されました baz/を追加中です(入=0)(出=0)(0%格納されました) baz/bar/を追加中です(入=0)(出=0)(0%格納されました) baz/bar/Bar.classを追加中です(入=475)(出=320)(32%収縮されました) baz/foo/を追加中です(入=0)(出=0)(0%格納されました) baz/foo/Foo.classを追加中です(入=475)(出=320)(32%収縮されました)
これで JAR ファイル baz.jar が生成されます。JAR ファイルの内容は次のコマンドで閲覧することができます。
jar tf ファイル名
$ jar tf baz.jar META-INF/ META-INF/MANIFEST.MF baz/ baz/bar/ baz/bar/Bar.class baz/foo/ baz/foo/Foo.class
JAR ファイルを利用する場合は環境変数 CLASSPAT かコマンドのオプション -classpath (-cp) でJAR ファイル名を指定します。
$ javac -cp baz.jar sample160.java $ java -cp baz.jar:. sample160 package foo 10 package bar 20
コマンド java で sample160 を実行する場合、CLASSPATH を設定するとカレントディレクトリの探索を行わないので、sample160 を見つけることができなくなります。この場合、CLASSPATH にカレントディレクトリ ( . ) を追加してください。パスの区切り記号は、Unix 系の OS ではコロン ( : ) を、Windows ではセミコロン ( ; ) を使います。これで複数のパスや JAR ファイルを指定することができます。探索の順序は左から右になります。
マニフェストファイルで main() が定義されているクラスを指定すると、実行可能な JAR ファイルを作成することができます。名前は何でもかまいません。次に示すように Main-Class: の後ろに main() が定義されているクラスを指定するだけです。
リスト : Manifest.txt Main-Class: sample160
jar コマンドではオプション m と引数に Manifest.txt を指定します。
$ jar cvfm sample.jar Manifest.txt sample160.class baz マニフェストが追加されました sample160.classを追加中です(入=496)(出=348)(29%収縮されました) baz/を追加中です(入=0)(出=0)(0%格納されました) baz/bar/を追加中です(入=0)(出=0)(0%格納されました) baz/bar/Bar.classを追加中です(入=475)(出=320)(32%収縮されました) baz/foo/を追加中です(入=0)(出=0)(0%格納されました) baz/foo/Foo.classを追加中です(入=475)(出=320)(32%収縮されました) $ jar tf sample.jar META-INF/ META-INF/MANIFEST.MF sample160.class baz/ baz/bar/ baz/bar/Bar.class baz/foo/ baz/foo/Foo.class
これで、Manifest.txt の内容が JAR ファイル内の MANIFEST.MF にコピーされます。なお、baz.jar には main() を定義しているクラスがないので、引数に sample160.class も指定する必要があります。
JAR ファイルを実行するにはオプション -jar を使います。それでは実行してみましょう。
$ java -jar sample.jar package foo 10 package bar 20
import 文で static を指定すると、クラス名を指定せずにスタティックメソッドやスタティックなフィールド変数にアクセスできるようになります。
import static パッケージ名.クラス名.staticな変数名またはメソッド名; import static パッケージ名.クラス名.*;
たとえば、クラス java.lang.Math には便利な数学関数が定義されていますが、static import すれば Math を省略してアクセスすることができます。
リスト : import static の使用例 import static java.lang.Math.sqrt; import static java.lang.Math.PI; public class sample161 { public static void main(String[] args) { System.out.println(sqrt(2.0)); System.out.println(PI); } }
$ javac sample161.java $ java sample161 1.4142135623730951 3.141592653589793