Javaのソースコード(.java)を、javacコマンドでコンパイルするときパスの指定や、実行時のパス指定がややこしいと感じたので、今回は、そのあたりを整理して、実際にいろいろなパターンで、コンパイル、実行していきます。
それではやっていきます!
参考文献
はじめに
「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。
Javaでデザインパターンの記事一覧
Javaのソースコード(.java)を、javacコマンドでコンパイルすると、中間コードやバイトコードなどと呼ばれるクラスファイル(.class)が作られます。このクラスファイルは、JavaVM(Java Virtual Machine)は、Java仮想マシンなどと呼ばれる、JVMの上で動きます。
通常のプログラム(ネイティブプログラム)は、直接CPUで実行できるプログラムですが、Javaの場合は、バイトコードという中間コードを作ることで、Windows、Linux、MACのどのプラットフォームでも動くようにしています。
Javaは、IDE(統合開発環境)を使って開発する場合、このようなことは意識しなくても出来てしまいます。しかし、統合開発環境でトラブルが起きたときは、その解決には、これらの知識が必要になります。
デザインパターンを学びながら、こういったトラブルを解決できる知識も身に着けていきたいと思います。
また、今回は Ubuntu22.04 を使います。ディレクトリ構造の表示に、treeコマンドを使っています。
インストールは、以下のようにします。
$ sudo apt install tree
また、通常のtreeコマンドの出力は文字化けする場合があるので、--charset=C
を付けて実行します。毎回付けるのは面倒なので、エイリアスを設定しています。
alias tree='tree --charset=C'
エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
それでは、やっていきます。
JavaプログラムがJVMで実行されるまでの流れ
マニュアルを見ながらやっていきます。
docs.oracle.com
Javaのバージョンを確認します。
$ 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のアプリケーションで試します。
public class Main {
public static void main(String args[]){
System.out.println( "Hello world !" );
}
}
Main.javaをコンパイルします。
$ javac Main.java
$ ls
Main.class Main.java
Main.classが生成されました。
Main.classを実行してみます。
$ java Main
Hello world !
ソースコードがカレントディレクトリではなく別ディレクトリにある場合
ディレクトリを作成します。先ほどのMain.classは削除しておきます。
mkdir -p src/com/example
rm Main.class
src/com/exampleにSub.javaを作ります。
public class Sub {
public static void main(String args[]){
System.out.println( "Hello Sub !" );
}
}
Sub.javaをコンパイルします。
javac(コンパイラ)コマンドは、コマンドライン引数に、ソースファイルを指定する、ということを意識しておくと混乱しないと思います。
$ javac src/com/example/Sub.java
$ tree
.
|-- Main.java
`-- src
`-- com
`-- example
|-- Sub.class
`-- Sub.java
3 directories, 3 files
Sub.classが生成されました。
Sub.classを実行します。-cp
は、クラスファイルのパスを指定するもので、-classpath
でもよいです。
javaコマンドはクラスを引数に取るので、Sub.classとはならないことに注意です。
$ java -cp src/com/example Sub
Hello Sub !
パッケージに所属している場合
パッケージに所属したソースコードを作ります。
src/com/exampleにPac.javaを作ります。
package com.example;
public class Pac {
public static void main(String args[]){
System.out.println( "Hello Pac !" );
}
}
Pac.javaをコンパイルします。ここまではSub.javaと同じですね。
$ javac src/com/example/Pac.java
$ tree
.
`-- src
`-- com
`-- example
|-- Pac.class
|-- Pac.java
|-- Sub.class
`-- Sub.java
3 directories, 4 files
Pac.classが生成されました。
Pac.classを実行します。この実行方法は、Sub.classと異なります。
$ java -cp src/ com.example.Pac
Hello Pac !
慣れないうちはややこしいですね。
クラスファイルを別ディレクトリに出力する場合
Pac.classとSub.classを削除しておきます。
クラスファイルを格納するディレクトリとして、classesを作成しておきます。
$ rm src/com/example/Pac.class src/com/example/Sub.class
$ mkdir classes
$ tree
.
|-- classes
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
4 directories, 2 files
Sub.javaをコンパイルします。
$ javac -d classes src/com/example/Sub.java
$ tree
.
|-- classes
| `-- Sub.class
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
classesにクラスファイルが出力されました。
Sub.classを実行してみます。クラスパスの設定が先ほどと変わっているので注意です。
$ java -cp classes/ Sub
Hello Sub !
では、次は、Pac.javaをコンパイルします。
$ javac -d classes/ src/com/example/Pac.java
$ tree
.
|-- classes
| |-- Sub.class
| `-- com
| `-- example
| `-- Pac.class
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
6 directories, 4 files
コンパイル方法は、Sub.javaと同じですが、クラスファイルはパッケージに合わせて、自動的に作成されたディレクトリに出力されました。
Pac.javaを実行します。
$ java -cp classes/ com.example.Pac
Hello Pac !
クラスパスの指定が変わってるだけで、あとは先ほどと同じですね。
クラス間に依存関係がある場合(パッケージに所属してない場合)
まず、classファイルを削除しておきます。
$ rm -rf classes/*
Main.javaを復活させて、Subクラスを使うように変更します。
Subクラスのmainメソッドをコールするとき、引数が必要なので、適当な文字列を作成しています。
public class Main {
public static void main(String args[]){
Sub sub = new Sub();
String[] str = {"main", "sub"};
sub.main(str);
}
}
現在の状態です。
$ tree
.
|-- Main.java
|-- classes
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
Main.javaをコンパイルします。Subクラスに依存しているので、ソースパスを指定します。
-cp
(クラスパス指定)でも動作します。マニュアルによると、「ソースパスが指定されていない場合、クラスパスからソースファイルを検索する」とありました。ソースパスは、クラスパスのように短縮形がないので、クラスパスを使う人が多いのかもしれません(笑)
$ javac -sourcepath src/com/example Main.java
$ tree
.
|-- Main.class
|-- Main.java
|-- classes
`-- src
`-- com
`-- example
|-- Pac.java
|-- Sub.class
`-- Sub.java
4 directories, 5 files
Main.classとともに、Sub.classも作成されています。
Mainクラスを実行します。クラスパスの指定が複数必要な場合は、:
で繋ぎます。
$ java -cp .:src/com/example/ Main
Hello Sub !
Subクラスから出力されていますね、成功です。
次は、classesディレクトリにクラスファイルを出力するようにしてみます。
$ rm Main.class src/com/example/Sub.class
$ tree
.
|-- Main.java
|-- classes
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
4 directories, 3 files
$ javac -d classes/ -sourcepath src/com/example Main.java
$ tree
.
|-- Main.java
|-- classes
| |-- Main.class
| `-- Sub.class
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
4 directories, 5 files
Mainクラスを実行します。今度はクラスパスの指定が1か所だけでいいですね。
$ java -cp classes/ Main
Hello Sub !
クラス間に依存関係がある場合(パッケージに所属している場合)
次は、パッケージ版をやってみましょう。
まず、classファイルを削除しておきます。
$ rm -rf classes/*
Mainクラスで、Subクラスを呼び出していたところを、Pacクラスを呼び出すように変更します。パッケージなので、import文を使います。
import com.example.Pac;
public class Main {
public static void main(String args[]){
Pac pac = new Pac();
String[] str = {"main", "sub"};
pac.main(str);
}
}
Main.javaをコンパイルします。
$ javac -sourcepath src Main.java
$ tree
.
|-- Main.class
|-- Main.java
|-- classes
`-- src
`-- com
`-- example
|-- Pac.class
|-- Pac.java
`-- Sub.java
4 directories, 5 files
Subクラスのときと同様に、Pacクラスもコンパイルされました。
では、Mainクラスを実行します。クラスパスはMainクラスとPacクラスの両方が見えるようにします。
$ java -cp .:src Main
Hello Pac !
成功です。
では、最後に、classesディレクトリにクラスファイルを生成するようにしてみます。
まず、クラスファイルを削除しておきます。
$ rm Main.class src/com/example/Pac.class
$ tree
.
|-- Main.java
|-- classes
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
4 directories, 3 files
Main.javaをコンパイルします。
$ javac -d classes/ -sourcepath src Main.java
$ tree
.
|-- Main.java
|-- classes
| |-- Main.class
| `-- com
| `-- example
| `-- Pac.class
`-- src
`-- com
`-- example
|-- Pac.java
`-- Sub.java
6 directories, 5 files
続いて、Mainクラスを実行します。クラスパスが1か所になるのがいいですね。
$ java -cp classes/ Main
Hello Pac !
成功です!やってるうちに慣れてきたと思います!
おわりに
パッケージが入ってくると、ややこしいですが、整理できたと思います。
今回の内容が参考になれば幸いです。
最後までお読みいただき、ありがとうございました。