javacコマンド入門

# 第３章 ソースコードをコンパイルする

## Javaにおけるコンパイルとは
コンパイルとは、平たく言えば人間にわかるように書かれたソースコードを
機械が理解できる機械語に翻訳する作業です。

Javaのコンパイルの仕組みは独特で、javacコマンドはソースコードを
Javaバイトコード(中間コード)というものにコンパイルします。

なぜ、機械語に変換しないのかというと、
Javaの基本理念「Write once, run anywhere.」の実現のためです。
ソースコードを直接機械語に変換してしまうと、コンパイルされた機械語は
コンパイルを実行した環境に「依存」するようになりますが、
JVMにはJavaバイトコードを機械語に変換する機能が実装されているため、
JVMさえ導入すればどのような環境であってもJavaを実行できるようになります。
つまり、「ソースコードを一度書いてしまえば、環境が異なっても実行できる」のです。

## javacコマンドの概要

javacを実行した際のコンパイルは、以下のような動きで行われます。

1. 指定したクラスを解析し、依存関係のある型を検索する
2. 検索で該当した型をコンパイルする

ここで重要になるのが、「1. 型の検索」です。
Javaは、コンパイルの際、指定したクラスが必要とする全ての型の情報を必要とします。
必要とする型には、具体的には以下のものがあります。

- ソースコードで使用されているクラス、インターフェース
- 使用クラスで暗黙的に継承、実装されているクラス、インターフェース

ここで注意するのが、暗黙的に継承、実装されているクラス群です。
例えばjava.util.ArrayListクラスをコンパイルするには、ArrayListが継承する
Serializable, Iterable<E>, Collection<E> などのインターフェースの情報も必要としますが、
ソースコードにそれらの情報は現れません。

### 検索の順序

型の検索対象にはいくつかの領域があり、それらを検索していく順序は決まっています。
検索対象の領域には以下のものがあります。(検索される順序で並んでいます)

- ブートストラップ・クラス  
JDKのコアAPI（例：java.lang.*)などを含むクラス群です。  
一般的には```<JAVA_HOME>/lib```に配置されています。
- 拡張機能クラス
拡張用ディレクトリに配置されたクラス群です。  
一般的には```<JAVA_HOME>/lib/ext```に配置します。  
※Java8以降では廃止された仕様のため、利用できません。
- ユーザクラス  
CLASSPATH環境変数、または-classpathオプションに設定されたパスに配置します。  
上記を設定していない場合は、カレントディレクトリが設定されます。

上記の検索順序は、
実行時にクラス情報をメモリへとロードするクラスローダにおいても同じです。
クラスローダの詳細については「[クラスローダとは]()」を参照してください。

javacを実行すると、上記の順序で検索が行われ、
同じ型が複数検出された場合は検索の優先度が高い領域の情報が使用されます。

### 検索結果に対する挙動

コンパイル時、型の検索で見つかるファイルは状況によって
クラスファイル、ソースファイル、またはその両方である場合があります。
javacの行う処理は、見つかったファイルの種類や状況によって以下の3つに分かれます。

【クラスファイルのみが見つかった場合】
　コンパイルは行わず、クラスファイルをそのまま使用する

【ソースファイルのみが見つかった場合】
　ソースファイルをコンパイルし、生成したクラスファイルを使用する

【両方が見つかった場合】
　〜クラスファイルがソースファイルより古い場合〜
　　ソースファイルをコンパイルし、最新の状態で生成したクラスファイルを使用する
　〜上記以外の場合〜
　　コンパイルは行わず、クラスファイルをそのまま使用する

### クロスコンパイル

開発をしていると、Java2 SDKを使っているので、javacはJava2 SDKの
ブートストラップクラスに対してコンパイルを行うが、Java 2 SDK ではなく JDK 1.4 の
ブートストラップクラスを使用したいというような場合も発生します。

こういった場合は、```-target 1.4```オプションにより、1.4 VM と互換性のあるクラスファイル
を生成し、```-bootclasspath```および```-extdirs```を使ってJDK 1.4 のブートストラップクラス
に対してコンパイルを行うように指定します。

このように、バージョンなどをまたいだコンパイルを行う機能を
**クロスコンパイル**と言います。

## javacコマンドのオプション

Javaのコンパイルについて一通りの用語を理解したところで、
各種設定、指定を行うためのオプションについて見ていきます。

### 主要なオプション

- -encoding <文字コード>  
ソースファイルの文字コードを指定する。デフォルトでは
Javaプラットフォームのコンバータ(ソースコード↔︎Javaバイトコードの変換を行う)  
に設定されているエンコードを使用する。
- -sourcepath <パス>  
ソースファイルの配置先を指定する。デフォルトではユーザクラスパスが指定されている。
- -d <パス>  
クラスファイルの配置先を指定する。デフォルトではユーザクラスパスが指定されている。
- -classpath(もしくは-cp) <パス>  
ユーザクラスの検索先を指定する。  
複数選択時はセミコロン(Windows)、またはコロン(Unix)で区切る。
- -source <バージョン>  
ソースコードに関して、特定のJDKバージョンのみ受け入れを許可する。

### デバッグ系のオプション
- -verbose  
このオプションを指定しない場合、コンソールには指定したクラス以外の  
型の検索で見つかったクラスに関するコンパイル情報などは出力されない。  
verboseオプションの指定により、それらの詳細な情報を見ることができる。
- -g  
ローカル変数を含むすべてのデバッグ情報を生成する。  
デフォルトでは、行番号およびソースファイル情報だけが生成される。
- -g:{keyword list}  
コンマで区切られたキーワードリストにより指定されたデバッグ情報だけを生成する。  
    - none  
    デバッグ情報を出力しない  
    - source  
    ソースファイルのデバッグ情報  
    - lines  
    行番号のデバッグ情報  
    - vars  
    局所変数のデバッグ情報

### クロスコンパイル系のオプション
- -target <バージョン>  
指定されたバージョンのVM上で動作するクラスファイルを生成する。  
デフォルトでは、JDK5 VMと互換性のあるクラスファイルを生成する。  
ただし、-source 1.4 以下のオプションが使用されている場合のみ、デフォルトは1.4となる。  
javac でサポートされているバージョンは次のとおり。  
    - 1.1  
    JDK 1.1 以降の VM で実行するクラスファイルを生成。
    - 1.2  
    JDK 1.2 より前の VM 上では動作しないクラスファイルを生成。
    - 1.3  
    JDK 1.3 より前の VM 上では動作しないクラスファイルを生成。
    - 1.4  
    JDK 1.4 より前の VM 上では動作しないクラスファイルを生成。
    - 1.5  
    JDK 5 の VM のみと互換性のあるクラスファイルを生成。
    - 5  
    1.5 と同義。
- -bootclasspath <パス>  
指定されたブートクラスに対してクロスコンパイルを行う。ユーザクラスパスと同様に、  
ブートクラスパスの複数のエントリはセミコロンまたはコロンで区切る。  
ブートクラスパスには、ディレクトリ、JARアーカイブ、ZIPアーカイブを指定できる。

## 実行

UseCommons.javaの実行には、以下の情報が必要です。

- メインメソッドを持ったクラス(UseCommons.java)
- 実行クラス内で使用されているクラス情報(StrFactory.java、commons-lang3-3.10.jar)

上記の情報を集めるため、javacコマンドは以下の情報を必要とします。

- 実行するクラスであるUseCommonsの配置場所
- 実行クラス内で使用されているStrFactoryとcommons-lang3-3.10.jarへのクラスパス
    - StrFactoryが配置されているパッケージのパッケージルート
    - commons-lang3-3.10.jarの配置場所  
    ※JARファイルとクラスファイルでは指定方法が違うため、設定内容の表記が若干違います

具体的なコマンドは以下の通りです。

```shell
# カレントディレクトリは/java-sample/src
$ javac -cp /java-sample/lib/commons-lang3-3.10.jar:/java-sample/src com/example/app/UseCommons.java
```

以下はクラスパスの指定です。

```shell
# JARファイルのクラスパス指定(ファイルまで指定する)
/java-sample/lib/commons-lang3-3.10.jar
# クラスファイルのクラスパス指定(ファイルまで指定しない)
/java-sample/src
# 上記のように、複数のクラスパスを指定する場合は:(Windowsの場合は;)で連結する
-cp /java-sample/lib/commons-lang3-3.10.jar:/java-sample/src
```

最後に実行するクラスを指定します。

```shell
# 例ではカレントディレクトリからの相対パスで指定している
com/example/app/UseCommons.java
```

以下、実行結果です。
<img width="349" alt="スクリーンショット 2020-05-25 21.23.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619923/ead96bd3-6ea7-33a1-7c78-c5d9ff2a4ac6.png">

見ると、xxx.javaと同名のxxx.classファイルが作成されています。
また、UseCommons内に定義されたクラスに関しては、UseCommons＄CommonsHelper.class
のように、```クラス名$インナークラス名.class```の形式のファイルが作成されます。
## javacコマンドのその他の使用例

### 複数ファイルのコンパイル
正規表現を使用すると、複数ファイルをコンパイルできます。
この場合、対象となるクラスが必要とするクラスへのクラスパスを
全て設定しておく必要があります。

```shell
# multipleディレクトリ内のクラス全てをコンパイルする
javac multiple/*.java
# multipleディレクトリ内の〜Targetというクラス全てをコンパイルする
javac multiple/*Target.java
```

### ソースファイルとクラスファイルの格納先を分ける
今までの例だと、同じディレクトリにクラスファイルが作成されるため、
JARファイルの作成時に余計な手間がかかる上、フォルダが整頓されず見づらくなります。
そういう時は、-dオプションでクラスファイルの格納先を、-sourcepathでソースファイルの
格納先を指定することができます。

```shell
# 実行すると、src直下に.javaファイルが、classes直下に.classファイルが格納される
javac -sourcepath src -d classes /src/com/example/app/Sample.java
```

### クロスコンパイル
以下の例は、前提条件としてJava2 SDKが設定されたプラットフォームを想定しています。
以下のコマンドを実行すると、Java2 SDKのJava２プラットフォームAPIではなく、
JDK1.4のコアAPIを使用してコンパイルが行われるようになります。

> 補足：-bootclasspathの設定値について
> -bootclasspathにはディレクトリのほかJARファイルやZIPファイルなどが
> 指定できるため、独自にブートストラップクラスをまとめて格納したJARファイルを
> 作成しておくと、複数のエントリを指定する手間が減ります。  

```shell
# -target 1.4：JDK1.4以上と互換性のあるクラスファイルを生成する
# -bootclasspath <パス>：指定したパスにあるブートストラップクラスを使用してコンパイルを行う
javac -target 1.4 -bootclasspath <ブートストラップクラスのアーカイブ配置先> Sample.java
```

> 補足：ブートストラップクラスの格納先の確認方法
> ブートストラップクラスの格納先は、
> システムプロパティsun.boot.class.pathに設定されています。

### コマンド行引数ファイル
javacにはコマンド行引数ファイルという、ファイルを使用して
複数クラスと複数オプションを指定する仕組みがありますが、これを使いこなす
コストを考えるとビルドツールを勉強した方がいいと思うので割愛します。

[メインページに戻る](https://qiita.com/maple_syrup/items/c0aa2eacb8f486dc52a8)