前回は、Javaのコンパイルと実行時のパスの関係を整理しました。
今回は、Jarを含めた場合をやっていきます。
それではやっていきます!
- 参考文献
- はじめに
- ベースとなる簡単なプログラム
- Jarファイルの作り方
- パッケージに所属していないクラスを利用する場合
- パッケージに所属していないクラスを利用する場合で別ディレクトリのクラスファイルを利用する場合
- パッケージに所属しているクラスを利用する場合
- -Cオプションを使って、ディレクトリを移動せずにJarファイルを作る方法
- 自然なディレクトリ構成の場合
- おわりに
参考文献
はじめに
「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。
・第1回:Javaでデザインパターンを学ぶ:Singletonパターン
・第2回:Javaでデザインパターンを学ぶ:Template Methodパターン
・第3回:Javaでデザインパターンを学ぶ:Observerパターン
・第4回:Javaでデザインパターンを学ぶ:Iteratorパターン
・第5回:Javaでデザインパターンを学ぶ:Factory Methodパターン
・第6回:Javaでデザインパターンを学ぶ:Stateパターン
・第7回:Javaでデザインパターンを学ぶ:Visitorパターン
・第8回:Javaでデザインパターンを学ぶ:Adapterパターン
・番外編:Javaのコンパイル方法(仕組み)をパッケージ含めていろいろ試してみる
・番外編2:Jarの作り方とJarを含んだJavaのコンパイル方法をいろいろ試してみる ← 今回
Javaのソースコード(.java)を、javacコマンドでコンパイルすると、中間コードやバイトコードなどと呼ばれるクラスファイル(.class)が作られます。このクラスファイルは、JavaVM(Java Virtual Machine)は、Java仮想マシンなどと呼ばれる、JVMの上で動きます。
通常のプログラム(ネイティブプログラム)は、直接CPUで実行できるプログラムですが、Javaの場合は、バイトコードという中間コードを作ることで、Windows、Linux、MACのどのプラットフォームでも動くようにしています。
今回もマニュアルを見ながらやっていきます。
今回は Jarファイルを対象に加えます。
Jarファイルとは、複数のクラスファイルや、画像、サウンドなどのファイルをZIPで圧縮したアーカイブファイルです。適切なMANIFESTファイルを用意することで、Jarファイル自体でJavaを実行することもできます。
エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
それではやっていきます!
ベースとなる簡単なプログラム
Javaのバージョンを確認します。Ubuntu22.04を使ってます。
$ java -version openjdk version "17.0.10" 2024-01-16 OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-122.04.1) OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-122.04.1, mixed mode, sharing)
前回で作成した簡単なJavaのアプリケーションを使います。
Main.javaは、パッケージに所属していない Sub.java と、パッケージに所属している Pac.java のクラスを利用しています。
$ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java `-- Sub.java 4 directories, 3 files
// Main.java import com.example.Pac; public class Main { public static void main(String args[]){ System.out.println( "Hello Main !" ); String[] str = {"Main", "Sub"}; Sub sub = new Sub(); Pac pac = new Pac(); sub.main(str); pac.main(str); } }
// Sub.java public class Sub { public static void main(String args[]){ System.out.println( "Hello Sub !" ); } }
// Pac.java package com.example; public class Pac { public static void main(String args[]){ System.out.println( "Hello Pac !" ); } }
Main.javaをコンパイルします。
$ javac -sourcepath src/:src/com/example/ Main.java $ tree . |-- Main.class |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.class |-- Pac.java |-- Sub.class `-- Sub.java 4 directories, 6 files
Main.classが生成されました。合わせて、Sub.class と Pac.class も生成されました。
Main.classを実行してみます。
$ java -cp .:src/:src/com/example/ Main
Hello Main !
Hello Sub !
Hello Pac !
クラスパスの指定が複雑になっています。. はMainクラスに必要で、src はPacクラスに必要で、src/com/example はSubクラスに必要です。
本来、Javaはパッケージとディレクトリ構造を合わせるべきなので、以下のような構成にする方が普通だと思います。
$ tree . |-- Main.java |-- Sub.java |-- classes `-- com `-- example `-- Pac.java 3 directories, 3 files
こういう構成にした場合についても最後に説明します。
Jarファイルの作り方
マニュアルは簡単なことだけが書かれていました。マニュアルの中に、Javaチュートリアルがあり、そこにJarに関するチュートリアル(ただし、Java8用)があったので、それを見ながらやっていきます。
Jarファイルは、jarコマンドで作ります。たくさんのオプションがありますが、ここではJarファイルを新規作成する場合を対象にやっていきます。
今回使用するオプションについて説明します。
- c:Jarファイルを新規作成します(fオプションが指定されている場合はファイルに出力され、指定されてない場合は標準出力に出力されます)
- v:詳細な情報を標準出力に出力します
- f jarfile:Jarファイルを指定します
- e entrypoint:指定したエントリポイントで実行可能にします
- t:Jarファイルの内容を一覧表示します
- -C dir:一時的にdirにディレクトリを変更します
パッケージに所属していないクラスを利用する場合
まず、SubクラスだけをJarファイルに格納して、Main.javaから利用する形でやってみます。
Main.javaを、Pacクラスを使わないように変更しておきます。
//import com.example.Pac; public class Main { public static void main(String args[]){ System.out.println( "Hello Main !" ); String[] str = {"Main", "Sub"}; Sub sub = new Sub(); //Pac pac = new Pac(); sub.main(str); //pac.main(str); } }
まず、クラスファイルを削除しておきます。
$ rm Main.class src/com/example/*.class $ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java `-- Sub.java 4 directories, 3 files
Sub.javaをコンパイルします。
$ javac src/com/example/Sub.java $ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java |-- Sub.class `-- Sub.java 4 directories, 4 files
Sub.classで、Jarファイルを作成します。
ここで注意点です。Jarファイルを作成するとき、対象のクラスファイルが、カレントディレクトリにクラスファイルが見えている状態で、Jarファイルを作らないとうまくいきませんでした(後述しますが、Jarファイルはディレクトリ構成を保持するので、カレントディレクトリ以外のクラスファイルをJarファイルに格納すると、うまくいきませんでした)。
ということで、Sub.classファイルのあるディレクトリまで移動してから、Jarファイルを作成します。
$ cd src/com/example/ $ jar cvf Sub.jar Sub.class マニフェストが追加されました Sub.classを追加中です(入=411)(出=283)(31%収縮されました) $ cd - $ mv src/com/example/Sub.jar classes/ $ tree . |-- Main.java |-- classes | `-- Sub.jar `-- src `-- com `-- example |-- Pac.java |-- Sub.class `-- Sub.java 4 directories, 5 files $ jar tf classes/Sub.jar META-INF/ META-INF/MANIFEST.MF Sub.class
Sub.jarを移動したのは、同じディレクトリに、Sub.javaやSub.classがあると、それを使って、Main.javaがコンパイルできてしまうためです。
Main.javaをコンパイルします。Main.javaはSubクラスに依存しているので単体ではコンパイルできません。クラスパスに、先ほど作成したJarファイルを追加します。
$ javac Main.java
Main.java:7: エラー: シンボルを見つけられません
Sub sub = new Sub();
^
シンボル: クラス Sub
場所: クラス Main
Main.java:7: エラー: シンボルを見つけられません
Sub sub = new Sub();
^
シンボル: クラス Sub
場所: クラス Main
エラー2個
$ javac -cp classes/Sub.jar Main.java
$ tree
.
|-- Main.class
|-- Main.java
|-- classes
| `-- Sub.jar
`-- src
`-- com
`-- example
|-- Pac.java
|-- Sub.class
`-- Sub.java
4 directories, 6 files
Jarファイルを使って、Main.javaをコンパイルすることが出来ました。
それでは、実行してみます。
クラスパスを指定しない場合は、カレントディレクトリを探してくれます(以下では、Hello Main !だけ実行できています)が、クラスパスを指定すると、カレントディレクトリは見てくれません。よって、Jarファイルがあるディレクトリと、Main.classがあるカレントディレクトリの両方をクラスパスに指定する必要があります。
$ java Main Hello Main ! Exception in thread "main" java.lang.NoClassDefFoundError: Sub at Main.main(Main.java:7) Caused by: java.lang.ClassNotFoundException: Sub at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ... 1 more $ java -cp classes/Sub.jar Main エラー: メイン・クラスMainを検出およびロードできませんでした 原因: java.lang.ClassNotFoundException: Main $ java -cp .:classes/Sub.jar Main Hello Main ! Hello Sub !
パッケージに所属していないクラスを利用する場合で別ディレクトリのクラスファイルを利用する場合
次は、Jarファイルにディレクトリ構造を持たせる場合です。現状の私の知識では、コンパイルできませんでした。
クラスファイルやJarファイルは削除した状態から始めます。Main.javaはSubクラスだけを参照しています。
$ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java `-- Sub.java 4 directories, 3 files
Sub.javaをコンパイルします。
$ javac src/com/example/Sub.java $ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java |-- Sub.class `-- Sub.java 4 directories, 4 files
別ディレクトリにクラスファイルがある状態で、Jarファイルを作ります。
$ jar cvf Sub.jar src/com/example/Sub.class マニフェストが追加されました src/com/example/Sub.classを追加中です(入=411)(出=283)(31%収縮されました)
Jarファイルの内容を確認します。Sub.classがディレクトリ構造を持っていることが分かると思います。
$ jar tf Sub.jar META-INF/ META-INF/MANIFEST.MF src/com/example/Sub.class $ tree . |-- Main.java |-- Sub.jar |-- classes `-- src `-- com `-- example |-- Pac.java |-- Sub.class `-- Sub.java 4 directories, 5 files
では、このJarファイルを使って、Main.javaをコンパイルしていきます。
Jarファイルをクラスパスに指定しただけでは失敗します。
$ javac -cp Sub.jar Main.java Main.java:7: エラー: シンボルを見つけられません Sub sub = new Sub(); ^ シンボル: クラス Sub 場所: クラス Main Main.java:7: エラー: シンボルを見つけられません Sub sub = new Sub(); ^ シンボル: クラス Sub 場所: クラス Main エラー2個
いろいろやってみましたが、コンパイルできませんでした。
パッケージに所属しているクラスを利用する場合
次は、パッケージに所属しているクラス(Pac.java)を利用する場合です。
まず、Main.javaをSubクラスは使わず、Pacクラスを使うように変更します。
import com.example.Pac; public class Main { public static void main(String args[]){ System.out.println( "Hello Main !" ); String[] str = {"Main", "Sub"}; //Sub sub = new Sub(); Pac pac = new Pac(); //sub.main(str); pac.main(str); } }
ソースファイルだけの状態から始めます。
$ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java `-- Sub.java 4 directories, 3 files
まず、Pac.javaをコンパイルします。
$ javac src/com/example/Pac.java $ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 4 files
Pac.javaは「package com.example」なので、srcディレクトリに移動してJarファイルを作る必要があります。
$ cd src/ $ jar cvf Pac.jar com/example/Pac.class マニフェストが追加されました com/example/Pac.classを追加中です(入=423)(出=294)(30%収縮されました) $ jar tf Pac.jar META-INF/ META-INF/MANIFEST.MF com/example/Pac.class $ cd .. $ tree . |-- Main.java |-- classes `-- src |-- Pac.jar `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 5 files
それでは、Main.javaをコンパイルします。
$ javac -cp src/Pac.jar Main.java $ tree . |-- Main.class |-- Main.java |-- classes `-- src |-- Pac.jar `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 6 files
コンパイルできたので、実行してみます。
$ java -cp .:src/Pac.jar Main
Hello Main !
Hello Pac !
成功しました。
-Cオプションを使って、ディレクトリを移動せずにJarファイルを作る方法
jarコマンドの-Cオプションを使うと、-Cオプションで指定したディレクトリに移動したことと同じ効果があります。
ソースファイル(.java)以外は、あらかじめ削除しておきます。
$ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.java `-- Sub.java 4 directories, 3 files
Pac.javaをコンパイルします。
$ javac src/com/example/Pac.java $ tree . |-- Main.java |-- classes `-- src `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 4 files
-Cオプションでsrcディレクトリの位置でJarファイルを作ります。クラスファイルの指定が、srcディレクトリからの指定になることに注意です。
$ jar cvf classes/Pac.jar -C src/ com/example/Pac.class マニフェストが追加されました com/example/Pac.classを追加中です(入=423)(出=294)(30%収縮されました) $ tree . |-- Main.java |-- classes | `-- Pac.jar `-- src `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 5 files
それでは、Main.javaをコンパイルします。
$ javac -cp classes/Pac.jar Main.java $ tree . |-- Main.class |-- Main.java |-- classes | `-- Pac.jar `-- src `-- com `-- example |-- Pac.class |-- Pac.java `-- Sub.java 4 directories, 6 files
では、実行してみます。
$ java -cp .:classes/Pac.jar Main
Hello Main !
Hello Pac !
自然なディレクトリ構成の場合
本来、Javaはパッケージとディレクトリ構造を合わせるべきなので、以下のような構成にする方が普通です。
$ tree . |-- Main.java |-- Sub.java |-- classes `-- com `-- example `-- Pac.java 3 directories, 3 files
こういう構成にした場合ののコンパイルです。オプションを指定する必要がありません。
$ javac Main.java $ tree . |-- Main.class |-- Main.java |-- Sub.class |-- Sub.java |-- classes `-- com `-- example |-- Pac.class `-- Pac.java 3 directories, 6 files
実行してみます。
$ java Main Hello Main ! Hello Sub ! Hello Pac !
さらに、実行可能なJarファイルを作成してみます。
オプションの f(Jarファイルを新規作成する)と、e:指定したエントリポイントで実行可能にする)の順序と、classes/Main.jar(Jarファイル)と Main(エントリポイント)の順序は同じにする必要があるので注意です。
$ jar cvfe classes/Main.jar Main ./*.class com/example/Pac.class マニフェストが追加されました Main.classを追加中です(入=566)(出=386)(31%収縮されました) Sub.classを追加中です(入=411)(出=283)(31%収縮されました) com/example/Pac.classを追加中です(入=423)(出=294)(30%収縮されました) $ tree . |-- Main.class |-- Main.java |-- Sub.class |-- Sub.java |-- classes | `-- Main.jar `-- com `-- example |-- Pac.class `-- Pac.java 3 directories, 7 files
実行してみます。
$ java -jar classes/Main.jar
Hello Main !
Hello Sub !
Hello Pac !
成功です!
おわりに
Jarファイルが入ってくると、さらにややこしくなりましたが、理解が深まりました!
一部、出来なかったところもありますが、解決したら、また追記したいと思います。
今回の内容が参考になれば幸いです。
最後までお読みいただき、ありがとうございました。