技術的雑談-Javaコンパイラ・ソースの基礎
環境
- JDK1.4、1.5どっちも。
- プラットフォーム問わず
目的
何でJavaのソースはあんなにディレクトリがたくさんあるのか理解する
内容
Javaではソースについて以下の3つのお約束があります。
- ソースファイルは「*.java」、コンパイルされたファイル(classファイル)は「*.class」
- 基本的に、ソースのファイル名と中のClass名は同じにする。
- ソースの置いてあるディレクトリ階層はClassのPackage階層に合わせる
(ルール3は別に守らなくてもコンパイルは可能ですが、著しくめんどくさくなります。)
ソース、クラス(コンパイルした後の生成物)はpackage(C++で言うところのNamespace)ごとにディレクトリが掘られるのが普通です。
例えば、以下のようなソース構成だったとします。
[ディレクトリ] ./ /src /com /hoge /fuga TestClass.java /mugyo Sub.java /bin
[TestClass.java] package com.hoge.fuga; public class TestClass { }
[Sub.java] package com.hoge.mugyo; // SubはTestClassを参照している public class Sub extends com.hoge.fuga.TestClass { }
この状態で「TestClass」に対して下記のようにjavacを実行すると、
(./ ディレクトリにいる) javac -sourcepath ./src -d ./bin ./src/com/hoge/fuga/TestClass.java
[ディレクトリ] ./ /src /com /hoge /fuga TestClass.java /mugyo Sub.java /bin /com /hoge /fuga TestClass.class
というファイルが作られます。
ディレクトリはコンパイラによって勝手に作られます。
javacコンパイラはコンパイルに必要な依存Classを以下の方法で探してきます。
- 指定されているソースツリー(-sourcepathで示されるディレクトリ以下)
- javacに-classpathで指定されているjar、もしくはディレクトリ
- JDK標準ライブラリー (普通は${JAVA_HOME}/jre/lib以下のjar)
先程生成されたTestClass.classを消して、
javac -sourcepath ./src -d ./bin ./src/com/hoge/mugyo/Sub.java
を実行しても、
[ディレクトリ] ./ /src /com /hoge /fuga TestClass.java /mugyo Sub.java /bin /com /hoge /fuga TestClass.class /mugyo Sub.class
と、Sub.classだけでなくTestClass.classも作られます。
これは -sourcepath ./srcが指定されているのでコンパイラが自動的にpackage構造を理解して必要なClassを探してくる為です。
その過程でコンパイラはTestClassも依存性があると判断して勝手にコンパイルします。
また、./src/com/hoge/fuga/TestClass.javaがない状態でも、./bin/com/hoge/fuga/TestClass.classがあるなら、
javac -sourcepath ./src -d ./bin -classpath ./bin com/hoge/mugyo/Sub.java
と実行する事により、コンパイラがSub.javaが依存するコンパイル済みのTestClass.classを探してきて、コンパイルを完了します。
上記からもわかるように、-sourcepath、-classpathはその下にあるpackage階層をディレクトリ階層によって理解しますので、ソースもclassもpackageの階層どおりに掘っておく事が望ましいです。
実際は、1つ1つ指定すればソースは階層を無視した状態(例えば、全てのpackageのソースを1つのディレクトリにフラットに置くとか)でもコンパイルは通ります。
しかし、Class毎の依存関係をコンパイラが自動的に判断できない為、ものすごく面倒になります。
(人間が人力で依存関係を解析して、-classpathに与えてやる必要があります。)
履歴
2005/7/15 -- 初版発行
技術的雑談に戻る