最近JVM上で動く言語をJavaで作っているのですが、Javaのパッケージの分け方や分割コンパイルで生成されたclassファイルの実行方法をよく忘れるのでメモしておきます。
package
クラスの数が増えると関連するクラスはどこかにまとめた方が分かりやすくなります。その機能を提供するのがpackageです。
普通、パッケージ名は所有するドメインを逆順に並べたものを使用しますが、説明のためここではその慣習に従いません。
Main.java
import fruit.bara.Apple; import fruit.mikan.Orange; public class Main {...}
import package名.class名ですね。こんな省略記法もあります。
Main.java
package Main; import fruit.bara.*; import fruit.mikan.*; public class Main {...}
import package名.* です。これでfruit.baraパッケージとfruit.mikanパッケージに属する全てのクラスを利用できるようになります。
ただ、ファイルの配置を考えないと実行することはできません。後述します。
なお、以下のように書くことはできません。
コンパイル
$ javac -Xlint:all,-serial Apple.java Orange.java Main.java
lintの警告をserial関連を除き全て出力するようにしています。
コンパイルはできますがこのままjavaコマンドで実行するとエラーになります。
パッケージとディレクトリ階層
パッケージに所属するclassファイルは正しいディレクトリに格納しないと実行できません。ディレクトリ階層のルールは以下のようになります。
「クラスパスが通っているディレクトリ/package文で宣言したパッケージ名の'.'(カンマ)を'/'(スラッシュ)に読み替えてパスにしたもの」
サンプルコードの例で言うと、
package fruit.bara;
だったので、
「クラスパスの通ったディレクトリ/fruit/bara」にApple.classを配置する必要があります。
実行
classファイルが入るディレクトリをbuildとします。
. ├── Apple.java ├── Main.java ├── Orange.java └── build ├── Main │ └── Main.class └── fruit ├── bara │ └── Apple.class └── mikan └── Orange.class
$ ls Main.java Main.java Orange.java build $ java -cp build/ Main.Main
cpオプションで./buildをクラスパスとして指定しています。javaコマンドの引数は起動したいクラス(=main関数のあるクラス)のFQCN(パッケージを含めたクラス名)です。FQCNを指定することにより、クラスパスを起点としてMain.Main.classが検索されます。
おまけ:-dオプション
javacコマンドに-dオプションをつけるとオプションの引数で指定したディレクトリにclassファイルを出力できるようになります。さらにすごいのが、上で行ったような正しいディレクトリへの格納作業も自動で行ってくれます。
$ javac -d ./build Apple.java Orange.java Main.java $ tree . ├── Apple.java ├── Main.java ├── Orange.java └── build ├── Main │ └── Main.class └── fruit ├── bara │ └── Apple.class └── mikan └── Orange.class
これは便利・・・!