本記事ではjavaでコンパイルする際に利用するクラスパスとパッケージについてを理解するために行ったことを備忘録としてまとめていきます。
[java] クラスパスとパッケージを理解するために行ったこと
- 環境
- 実施したこと
- コンパイルとビルド
- 分割コンパイルとビルド(クラスパスについて)
- パッケージ化とビルド
環境
- win10
- jdk11※事前にパスは通しておく
実施したこと
用意するフォルダ構成とファイルは以下の通りです。
C:\Users\yuya\javatest>dir
C:\Users\yuya\javatest のディレクトリ
2021/02/15 01:49 <DIR> .
2021/02/15 01:49 <DIR> ..
2021/02/15 01:48 229 SampleMain.java
- javatest(プロジェクトのフォルダ)
- SampleMain.java
public class SampleMain {
public static void main(String[] args){
System.out.println("SampleMain Loaded");
}
}
コンパイルとビルド
SampleMain.javaをjavacコマンドでコンパイルします。
C:\Users\yuya\javatest>javac SampleMain.java
C:\Users\yuya\javatest>dir
C:\Users\yuya\javatest のディレクトリ
2021/02/15 01:50 <DIR> .
2021/02/15 01:50 <DIR> ..
2021/02/15 01:50 469 SampleMain.class
2021/02/15 01:48 229 SampleMain.java
SampleMain.classが生成されるのでこれをjavaコマンドで実行します。
C:\Users\yuya\javatest>java SampleMain
SampleMain Loaded
分割コンパイルとビルド(クラスパスについて)
先ほど作成したclassファイルは削除して、分割コンパイル用に`Sample.java`ファイルを追加します。
C:\Users\yuya\javatest>dir
C:\Users\yuya\javatest のディレクトリ
2021/02/15 12:16 <DIR> .
2021/02/15 12:16 <DIR> ..
2021/02/15 12:15 151 Sample.java
2021/02/15 01:54 212 SampleMain.java
Sample.java
public class Sample {
public Sample(){}//コンストラクタ
public void print(){
System.out.println("Sample Loaded");
}
}
SampleMain.java
public class SampleMain {
public static void main(String[] args){
System.out.println("SampleMain Loaded");
Sample sample = new Sample();
sample.print();
}
}
これで準備完了なのでコンパイルしてビルドします。
C:\Users\yuya\javatest>dir
C:\Users\yuya\javatest のディレクトリ
2021/02/15 12:57 <DIR> .
2021/02/15 12:57 <DIR> ..
2021/02/15 12:50 151 Sample.java
2021/02/15 12:52 194 SampleMain.java
C:\Users\yuya\javatest>javac SampleMain.java
C:\Users\yuya\javatest>dir
C:\Users\yuya\javatest のディレクトリ
2021/02/15 12:57 <DIR> .
2021/02/15 12:57 <DIR> ..
2021/02/15 12:57 395 Sample.class
2021/02/15 12:50 151 Sample.java
2021/02/15 12:57 486 SampleMain.class
2021/02/15 12:52 194 SampleMain.java
SampleMain.javaをコンパイルしただけなのに、なぜかSample.classも生成されたのは謎ですが、(恐らくコンパイラがSampleMainの中でSampleクラスがあるのを見てコンパイルしてるのかと思います。)
SampleMain.class、Sample.classが生成されて分割コンパイルが完了したので実行します。
C:\Users\yuya\javatest>java SampleMain
SampleMain Loaded
Sample Loaded
問題なく実行できてますね。
ここで以下二つの実験を行い、クラスパスを理解していきます。
1. Sample.classファイルを別のフォルダに移動してSampleMain.classを実行する
2. Sample.javaファイルを別のフォルダに移動してSampleMain.javaをコンパイルする
実験1. Sample.classファイルを別のフォルダに移動してSampleMain.classを実行する
以下のディレクトリ構成にして実行します。
C:\Users\yuya\javatest>tree /f
C:.
│ Sample.java
│ SampleMain.class
│ SampleMain.java
│
└─sub
Sample.class
この状態でSampleMainを実行します
C:\Users\yuya\javatest>java SampleMain
SampleMain Loaded
Exception in thread "main" java.lang.NoClassDefFoundError: Sample
at SampleMain.main(SampleMain.java:4)
Caused by: java.lang.ClassNotFoundException: Sample
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more
classファイルがあるのはカレントディレクトリ(SampleMain.class)とsubディレクトリ(Sample.class)ですが、SampleMainクラスを読み込み中に、Sampleクラスが見つからないエラーとなりました。
よって、全てのclassファイルが見つかる状態でなければSampleMain.classファイルの実行に失敗することがわかりました。
解決するには、Sampleクラスを見つかる状態にしてあげます。
javacコマンドはデフォルトでクラスの読み込み先が実行したディレクトリ(カレントディレクトリ)のみとなるので、
javacコマンドに、classファイルが置いてある場所を全て伝えるように変更します。
伝える方法がクラスパスです。
クラスパスの指定方法はJavacコマンドに-classpathオプション、または-cpオプションで指定を行います。
指定方法は以下の通りです。
C:\Users\yuya\javatest>java -classpath .;./sub SampleMain
SampleMain Loaded
Sample Loaded
カレントディレクトリとサブディレクトリの二つを指定する方法は「.;./sub」とします。「;」これで複数指定が出来ます。
-classpathや-cpを省略する技もあります。それは、クラスパスを環境変数に登録する方法です。環境変数にCLASSPATHを「.;./sub」と登録することでオプションの省略が可能です。
実験2. Sample.javaファイルを別のフォルダに移動してSampleMain.javaをコンパイルする
以下のディレクトリ構成にします。
C:\Users\yuya\javatest>tree /f
C:.
│ SampleMain.java
│
└─sub
Sample.java
SampleMain.javaをコンパイルします。
C:\Users\yuya\javatest>javac SampleMain.java
SampleMain.java:4: エラー: シンボルを見つけられません
Sample sample = new Sample();
^
シンボル: クラス Sample
場所: クラス SampleMain
SampleMain.java:4: エラー: シンボルを見つけられません
Sample sample = new Sample();
^
シンボル: クラス Sample
場所: クラス SampleMain
エラー2個
すると実験1と同様にSample.javaが見つからないというエラーでコンパイルに失敗します。
これもクラスパスがカレントディレクトリになっているので失敗しているというわけです。クラスパスはコンパイル時とビルド時で使用します。この場合のクラスパスは、コンパイルするためのファイルのありかがどこにあるかを示すものになります。
なので以下のように実行してあげればいいわけです。
C:\Users\yuya\javatest>tree /f
C:.
│ SampleMain.java
│
└─sub
Sample.java
C:\Users\yuya\javatest>javac -classpath .;./sub SampleMain.java
C:\Users\yuya\javatest>tree /f
C:.
│ SampleMain.class
│ SampleMain.java
│
└─sub
Sample.class
Sample.java
このようにコンパイルが可能となるわけです。その後の実行は実験1で行ったように実行が可能です。
C:\Users\yuya\javatest>java -classpath .;./sub SampleMain
SampleMain Loaded
Sample Loaded
ここまででクラスパスについてが理解できたはずです。クラスパスを指定することで、コンパイルを行ったりコンパイルしたclassファイルを実行してきました。次の章ではパッケージを絡めたクラスパスの関係を理解していきます。
パッケージ化とビルド
パッケージ化したファイルを読み込むことで、クラスパスの指定方法が少し変わってくることを理解していきます。ちなみにパッケージとは自分のファイルがどこにあるかを指定する方法です。
C:\Users\yuya\javatest>tree /f
C:.
│ SampleMain.java
│
└─sub
Sample.java
SampleMain.java
public class SampleMain {
public static void main(String[] args){
System.out.println("SampleMain Loaded");
sub.Sample sample = new sub.Sample();//ここを変更
sample.print();
}
}
今までならコンパイル時、subフォルダをclasspathに指定して、コンパイラに場所を教えてあげる必要があったが、パッケージを使っているため、それで場所を教えてあげることが出来るようになる。ちなみに、パッケージのディレクトリ指定はルートディレクトリからの指定方法となる。なので、以下の通りコンパイルして実行することが可能となる。
C:\Users\yuya\javatest>tree /f
フォルダー パスの一覧: ボリューム Windows-SSD
ボリューム シリアル番号は 528E-0B8D です
C:.
│ SampleMain.java
│
└─sub
Sample.java
C:\Users\yuya\javatest>javac SampleMain.java
C:\Users\yuya\javatest>tree /f
フォルダー パスの一覧: ボリューム Windows-SSD
ボリューム シリアル番号は 528E-0B8D です
C:.
│ SampleMain.class
│ SampleMain.java
│
└─sub
Sample.class
Sample.java
C:\Users\yuya\javatest>java SampleMain
SampleMain Loaded
Sample Loaded
なのでSample.classを以下のように移動して実行はできない
C:\Users\yuya\javatest>tree /f
C:.
│ SampleMain.class
│ SampleMain.java
│
├─classes
│ └─sub
│ Sample.class
│
└─sub
Sample.java
C:\Users\yuya\javatest>java SampleMain
SampleMain Loaded
Exception in thread "main" java.lang.NoClassDefFoundError: sub/Sample
at SampleMain.main(SampleMain.java:4)
Caused by: java.lang.ClassNotFoundException: sub.Sample
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more
Sample.classが見つからなくなって実行できなくなります。ですがこの場合はclasspathオプションの指定方法を以下のように指定することで実行が可能となります。
C:\Users\yuya\javatest>java -classpath .;./classes SampleMain
SampleMain Loaded
Sample Loaded
パッケージをクラスパスに指定する場合は、subディレクトリが置いてあるルートディレクトリを指定するということです。
ちなみに、パッケージがdir.sub;だった場合は、dirが置いてある場所をclassパスとしてあげればいいということです。
以上。