update 071006
「パッケージディレクトリ内でのプログラムの起動」を追加

パッケージに属するクラスとクラスパス


  パッケージに属さない、というかpackage宣言のないクラスのクラスパスは、そのクラス(クラスファイル)があるディレクトリです。しかし、パッケージに属するクラスのクラスパスは、そのクラスのクラスファイルがあるディレクトリの「ルートディレクトリ」です。このことは、Javaの基本ドキュメントの随所にちゃんと書かれているのですが、私のような鈍い人間は、実際に痛い思いをしないと身にしみません。

  以下に、その痛い思いの経緯を、簡略化した例で説明してみましょう。

【前提】私同様、多くのかたのCLASSPATHには、カレントディレクトリがドット'.'で指定されていると思います。その前提で話を進めます。

  その指定のないかたは、javacやjavaを動かす無オプションのコマンド行では、次のように指定してください:

    javac -classpath . MyApp.java
    java -classpath . MyApp


1.カレントディレクトリ(それはどこでもよい)の下に、pkgtestという名前のサブディレクトリを作ってください。

    mkdir pkgtest


2.pkgtestサブディレクトリの中へ、pkgtestパッケージに属するクラスをひとつ作ります:

    cd pkgtest

----Pk.java--------------------------------------------------------
package pkgtest;

public class Pk{

  public void printPk(){
    System.out.println("I am a Pk object from pkgtest package.");
  }
}
--------------------------------------------------------------------


3.pkgtestサブディレクトリにいる状態で、Pk.javaをコンパイルします:

    javac Pk.java

このPk.javaはほかのユーザクラスをなにも参照していないので、コンパイルはすんなりと終わります。


4.次に、同じくpkgtestサブディレクトリの中に、このPkをextendsしたクラスを作ります:

----Pe.java---------------------------------------------------------
package pkgtest;

public class Pe extends Pk{

  public void printPe(){
    System.out.println("I am a Pe object extending Pk.");
  }
}
--------------------------------------------------------------------


5.pkgtestサブディレクトリにいる状態で、このPe.javaをコンパイルします:

    javac Pe.java

そうすると、こんなエラーメッセージを食らいます:

    pe.java:3: シンボルを解決できません。
    シンボル: クラス Pk
    場所 : pkgtest.Pe の クラス
    public class Pe extends Pk{
                            ^

クラスパスにカレントディレクトリ'.'が含まれているんだから、当然、Pkにアクセスできるだろっ!

  これが、痛い思いのはじまりです。

  そして、やっと身にしみて知るのが、「パッケージに属するクラスのクラスパスはパッケージのルートディレクトリである」というJavaの基本ルールです。


6.上の5.のjavacコマンドは、正しくはこうです(pkgtestサブディレクトリにいる状態で):

    javac -classpath .. Pe.java

つまりつまり、この基本ルールでは、上の5.のようにやると、'.'で指示されるカレントディレクトリ(この場合はpkgtet)をルートとして、その下にさらに、pkgtestパッケージを収めたpkgtestディレクトリがある、という指定になってしまうのです:

    CURRENTDIR/pkgtest/pkgtest .... ありえない!!

ですから、『この、(無修飾なので)Peと同じパッケージに属するらしいPkは、カレントディレクトリ(pkgtest)の上のディレクトリ、すなわち'..'をルートとするパッケージに属するんだよ』というclasspathの指定が必要になります。

  たまたまCLASSPATHに'..'(カレントディレクトリのすぐ上)が含まれていた、という奇特なかたにかぎり、5.のようなjavacの起動でも無エラーとなります。


7.以上でやっと、pkgtestディレクトリのなかにPk.classとPe.classができましたから、元々の(最初の)カレントディレクトリに戻って、アプリケーションをひとつ作ってみましょう:

    cd ..

----PkgTest.java----------------------------------------------------
import pkgtest.*;

public class PkgTest{

  public static void main(String[] args){

    Pk p = new Pk();
    p.printPk();

    Pe e = new Pe();
    e.printPe();
  }
}
--------------------------------------------------------------------

  そして、PkgTestがPkやPeにアクセスできるために、気を利かせたつもりでこんなコンパイルをすると:

    javac -classpath pkgtest PkgTest.java

こんなエラーが出ます:

    PkgTest.java:1: パッケージ pkgtest は存在しません。
    import pkgtest.*;
    ^
    PkgTest.java:7: pk にアクセスできません。
    クラスファイル pkgtest/Pk.class は不正です。
    クラスファイル pkgtest.Pk に不正なクラスがあります。
    削除するか、クラスパスの正しいサブディレクトリにあるかを確認してください。
         Pk p = new Pk();
         ^
    エラー 2 個

  もうすでにお分かりと思いますが、基本ルール:「パッケージに属するクラスのクラスパスはパッケージのルートディレクトリである」に従うならば、カレントディレクトリの直下のpkgtestパッケージ(のルート)を指定するためのクラスパスはカレントディレクトリ、すなわち'.'です。すでにCLASSPATHに'.'が含まれているなら、javacの起動は。こうでなければならない:

    javac PkgTest.java

CLASSPATHに'.'が含まれていないかたは:

    javac -classpath . PkgTest.java

そして、ここまでの勉強を経験したかたは、上のコマンド行を見ても、もう、「えーっ、PkもPeも'.'(カレントディレクトリ)にはないぞ!。その下のpkgtestディレクトリにあるのにさあ!!」、という悲鳴を上げることはないでしょう。

  (ただし、上の教訓に対するひとつの例外として、パッケージに属するクラスがjarファイルに収まっているときには、そのjarファイルそのものをクラスパスに指定します。)

(071008追記)
8. パッケージディレクトリ内でのプログラムの起動
 pkgtestパッケージに属するPeやPkにmain()メソッドがあって、クラスをアプリケーションとして起動したいとき、カレントディレクトリ(パッケージルート)に居るなら:

java pkgtest.Pe

あるいは、

java -cp . pkgtest.Pe

などですが、pkgtestディレクトリに居る状態でプログラムを起動するためには、

java -cp .. pkgtest.Pe

のようにクラスパスを指定する必要があります。つまり、パッケージに属するクラスのクラスパスはパッケージルートであります。


●こんな記事を書いてる当人が、また馬鹿をやってしまいました(051027):
  あるWebアプリケーションのクラスのソースファイル群が、/WEB-INF/classes/songディレクトリにあります。各ソースの冒頭にはpackage song;宣言があります。これらをsongディレクトリの中でコンパイルします(JDK 1.6を使用):
javac Midi.java
上は、ライブラリをクラスパスで指定していないのでだめです。では、
javac -cp ../../lib/jsf-api.jar Midi.java
jsf-api.jarは/WEB-INF/libディレクトリにあるので、上ので良いはずです。でも、だめです。そうか、カレントディレクトリを忘れていた?!?(Linux上):
javac -cp .:../../lib/jsf-api.jar Midi.java
ところが、上でもまだだめです。正解は、あえて書きません。この記事を読んだかたは、お分かりのはずですから。


o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o-o
【蛇足】結局、あるディレクトリ(上の例ではpkgtest)以降に収められたJavaのパッケージは、Javaから見て、もはやOSの通常のファイルシステムのディレクトリ階層ではなくて、それらをたまたま間借りして実装されている、パッケージという別物、別世界である…したがって、その別物を収めているディレクトリ(“パッケージのルートディレクトリ”)=OSに属する部分の最末端(上の例では、PkgTestアプリケーションを収めたカレントディレクトリ)が、そのパッケージを参照するためのクラスパスである、というお話なのでしょう。JavaのパッケージはOSのファイルシステムとは別物、と考えれば、上のような基本ルールも納得いくと思います。

    ...OS file-system...CURRENTDIR/Java Package World ...
                         (ROOTDIR)

[問題]
  以下の二つのファイルを、すんなりコンパイルするためには、javacのオプションをどう設定すべきでしょうか?:
/* Tendon.java ... カレントディレクトリ内に置く */
public class Tendon{
}
-------------------------------------------------------------------------
/* Unadon.java ... カレントディレクトリ直下のunaディレクトリ内に置く */
package una;

public class Unadon{
  public Unadon(){
    Tendon t = new Tendon();
  }
}
[問題おわり]


***
*** このように、OSのファイルシステムとJavaのファイル/ディレクトリ認識との間
*** に難解な断絶が見られるもう一つの例が、jarファイルの場合です。
***
o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o_o-o-o-o-o

---end of text---