普段はC#やPHP、Pythonを使って開発をしているのですが、あるプロジェクトでJavaを使うことになり勉強してみました。しかし歴史のあるプログラミング言語のせいか、フレームワークを使った開発方法の記事ばかりで、IDEの裏でどのような処理が動いているのかといった基本的な動作がわからず、Webに書いてある通りにしかプログラムを書けない、という状態になってしまいました。

そこで今回自分が勉強してわかったことを、これからJavaを始める方達に向けて紹介したいと思います。

この記事はある程度プログラミング経験のある方が、初めてのJava開発をスムーズに始められるよう基本的な動作原理を説明することを目的にしています。Javaの文法などプログラミングの基本的な部分は説明しませんのでご注意ください。

1回目はJavaのコンパイルと実行を、IDEやビルドツールを使わずに行う方法を見たいと思います。

開発環境の準備

まずは開発環境を準備します。Javaの開発には「Java Development Kit (JDK)」というコンパイラやコアライブラリが含まれるツールが必要になります。JavaのJDKは歴史的に、Java言語の開発元は仕様のみを公開して、実装は各ベンダー / コミュニティが、商用 / オープンソースで開発する、というスタイルを取っています。そのため世の中にはいくつものJDKがあり、Oracle公式のOracle JDKや、Oracle JDKのオープンソース版のOpen JDKなどが有名です。JDKは初めはなんでも良いと思いますので、今回は無料のOpen JDKを使用する前提で進めます。

JDKをダウンロードしたら、binフォルダーにPathを通しておきましょう。

Hello Worldのコンパイルと実行

それではHello Worldを書いて実行してみます。

MySample.java


public class MySample {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

Javaでは、ファイル名とクラス名は同じにする必要があります。MySample.javaというファイルには、MySampleクラスを書く必要がありますので注意してください。

ファイルを作ったら、コンパイルします。コンパイルは javac コマンドです。コンパイルすると、.class と言う拡張子のファイルが作成されます。

Shell / コマンドプロンプト

$ javac MySample.java      # MySample.classが出力される

コンパイルしたファイルを実行するには、javaコマンドを使用します。

Shell / コマンドプロンプト

$ java MySample       # パラメータにはクラス名を指定する
> Hello World

javaコマンドのパラメータに指定するのは「クラス名」ですので注意してください。MySample.classというファイル名を指定するとエラーになります。

別ファイルのクラスを参照する

それでは次に、別のファイルに書かれたメソッドを使用する方法を見てみましょう。 まずは参照される側のファイルを作成します。

Greeting.java

public class Greeting {
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

次に、上記ファイルを呼び出す側のファイルを作成します。

MySample2.java

public class MySample2 {
    public static void main(String[] args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("World");
    }
}

それではコンパイルして実行してみましょう。

Shell / コマンドプロンプト

$ javac Greeting.java      # Greeting.classが出力される
$ javac MySample2.java      # MySample2.classが出力される
$ java MySample2      # MySample2を実行
> Hello World

クラスパス

ここで、MySample2クラスはどのようにGreetingクラスを見つけることができたのでしょうか。答えはJavaの「クラスパス」という仕組みにあります。Javaはあるクラスが要求された時、クラスパスに設定されたフォルダーを検索し、対象のクラスが見つかればそれをロードします (見つからなければ例外が発生します)。

クラスパスは、デフォルトでカレントディレクトリに設定されています。そのため上記の例では、MySample2クラスでGreetingクラスのインスタンスを生成しようとした時、JavaはカレントディレクトリからGreetingクラスを検索し、そこにあったGreeting.classをロードしてインスタンスを生成した、という流れになります。

パッケージ

今までは1つのフォルダーに複数のファイルを置いて実行していました。アプリケーションの規模が大きくなると、機能ごとにフォルダーを分けて管理することが多いと思います。

Javaでは、このフォルダーのことを「パッケージ」と呼びます。Hello Worldの例を、パッケージを分けて実装してみましょう。mypackage1 と mypackage2 という2つのフォルダを作って、以下のファイルを作成してください。

mypackage1/Greeting.java

package mypackage1;

public class Greeting {
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

mypackage2/MySample.java

package mypackage2;

import mypackage1.Greeting;

public class MySample {
    public static void main(String[] args) {
        Greeting greeting = new Greeting();
        greeting.sayHello("World");
    }
}

ファイルを作ったら、mypackage1フォルダーの親フォルダで、以下のコマンドを実行して確認しましょう。

Shell / コマンドプロンプト

$ ls      # mypackage1/2フォルダーの親フォルダでコマンドを実行
> mypackage1 mypackage2

$ javac mypackage1/Greeting.java    # Greeting.classが生成される
$ javac mypackage2/MySample.java    # MySample.classが生成される
$ java mypackage2.MySample    # MySampleを実行
> Hello World

Javaの世界では、フォルダー階層はパッケージとして表現され、ピリオド “.” で連結します。package文やimport文では、クラスパスからの相対パスをフォルダー階層として表現しているようなイメージです。

クラスパスは既定でカレントディレクトリですので、”import mypackage1.Greeting;” という文は、「mypackage1フォルダーのGreetingクラス」を表すことになります。

そのため、上記のjavac および java コマンドを別のフォルダーから実行しようとすると、例外が発生してコンパイル/実行することができません。

なお package文を記述しないと、クラスはデフォルトパッケージに含まれることになります。初めの例で同じフォルダーにあるGreetingクラスを参照できたのは、2つが同じデフォルトパッケージに含まれていたためです。

JARファイル

それでは次に、Java Archive (JAR) ファイルを作ってみます。JARファイルは複数のファイルをZip形式にまとめるためのもので、複数のクラスからなるライブラリやアプリケーションをまとめるために使用されます。

JARファイルは jar コマンドで作成できます。初めに作ったGreetingクラスをJARライブラリに見立てて、試してみましょう。

Shell / コマンドプロンプト

$ ls
> Greeting.class Greeting.java MySample.class MySample.java

$ jar -cvf Greeting.jar Greeting.class      # Greeting.jar が出力される

JARファイルはZip形式で圧縮されていますので、Zip解凍ソフトを使って展開でき、中身を見てみると、以下のようになっているのが確認できます。

Greeting.jarの中身

- META-INF
    | - MANIFEST.MF
- Greeting.class

MANIFEST.MFファイルはJDKのバージョンやエントリーポイントのクラスなどを記述する構成ファイルで、jarコマンドにより自動生成されます。

JARファイルの中身はフォルダ構成になっており、Javaは一つのディレクトリとして認識します。そのためJARファイルの中にあるクラスを参照する時は、JARファイルをクラスパスに追加する必要があります。(カレントディレクトリにJARファイルがあるからといって、JavaがJARファイルを自動でロードすることはありません)

クラスパスは java コマンドのパラメータで設定できますが、設定した場合、デフォルトのクラスパス (既定ではカレントディレクトリ) は上書きされて消えてしまいます。そのため、カレントディレクトリにあるクラスを実行する場合、参照するJARファイルとカレントディレクトリの両方をクラスパスに設定する必要があります。

JARファイルとカレントディレクトリをクラスパスに設定してMySample2を実行するコマンドは以下となります。

Shell / コマンドプロンプト

$ java -classpath Greeting.jar;. MySample2
> Hello World

クラスパスはセミコロン “;” で連結できるので、ここではGreeting.jarとカレントディレクトリ “.” を連結してクラスパスに設定しています。

複雑な操作を簡単にするために

ここまでで、javacコマンドで1つずつファイルをコンパイルし、jarコマンドでJARファイルにまとめ、javaコマンドにクラスパスを設定して実行する、という手順を見てきました。

今回使ったような簡単な例でも、PHPなど他の言語に比べて随分面倒だと感じられたと思いますし、数十、数百のクラスを使用するアプリケーションで、ファイルを1つずつコンパイルしなければならないというのは現実的ではありません。

そのためJavaではビルドツールが作られて、アプリケーションのビルドが自動化されるようになりました。最近のビルドツールは、必要なライブラリを自動的にダウンロードしてきたりと多機能になっていますが、基本的な動作はこの記事で解説したコンパイルとアーカイブです。

IDEを使うと、いきなりビルドツールのプロジェクトを作なければならず、ビルドツールを知らない初心者にはとっつきにくい印象を与えてしまっているのではないかと感じています。

IDEやビルドツールも色々ありますが、基本動作はどれも一緒です。過度に怖がらずに、まずは1つ選んでやってみて、徐々に慣れていくのが良いと思います。