Java
Vim
CentOS
新人プログラマ応援

linuxでJavaの動作確認をすることで、コンパイルや階層理解がグッと理解できた

先日、自社勉強会で、Javaのコンパイル、パッケージ管理について学習した。eclipseで環境が出来たものを編集することしかなかったので、コンパイルオプションを設定し、パッケージを作成することで、コンパイルによる動きやパッケージの管理方法をより理解する機会になった。
自身の復習も兼ねて、ここにアウトプットすることにする。

環境

・CentOS7.4
・java 1.8.0_171
・vi 7.4
・tree 1.6.0

準備

動作検証する為のディレクトリ作成。

# cd
# mkdir -p JAVA/src
# cd JAVA
# mkdir class

コンパイル&実行

treeコマンドで、カレントディレクトリからディレクトリ階層を見ると以下のようになる。

# tree --charset=c
.
|-- class
`-- src

まずは、srcに移動し、テスト用ソースファイルを作成してみる。

# cd src
# vi TestJava.java
TestJava.java
class TestJava {

    public static void main(String[] args){

        System.out.println("test");

    }

}

上記の内容で作成後、ディレクトリ階層を確認すると以下のようになる。

# tree ../ --charset=c
../
|-- class
`-- src
    `-- TestJava.java

作ったファイルをコンパイルする。
ここで、オプション指定せずにコンパイルするとsrc配下にclassファイルが作成されるが、作ったあとにclassファイルを別ディレクトリに移動をさせるのは面倒。eclipseでもこの辺は自動で行われていたので、感じてなかったが、手動で行う場合、作成時に指定ディレクトリ(classディレクトリ)に作成されるようにオプションを指定する。ディレクトリの指定は"-d"オプションを使用する。

# javac -d ../class TestJava.java

実行後、ディレクトリ階層の状況を確認。

# tree ../ --charset=c
../
|-- class
|   `-- TestJava.class
`-- src
    `-- TestJava.java

指定したclassディレクトリにファイルが作成されている。このようにソースファイルとクラスファイルは、勝手に誰でもソースを編集できる状況にしない為に分けて管理をする。

実行時、これをclassディレクトリ以外から実行するには、"-cp"オプションでファイルのパスを指定すると、カレントディレクトリにファイルがなくても実行できる。環境変数で設定するCLASSPATHをコマンドライン上で行う場合の方法になる。

# java -cp ../class TestJava
test

実行され、「test」と出力された。

パッケージ

クラスファイルが増えてきたら管理しやすくする為にパッケージを分けて管理する。また、複数人で開発をする際にたまたま同一名のファイルを作成してしまっても名前の衝突をしない為にも良い。
まずは、パッケージ用のディレクトリをsrcディレクトリに作成し、一旦クラスファイルを削除して検証。

# mkdir pack1
# mv TestJava.java pack1
# rm -rf ../class/TestJava.class
# cd pack1
# tree ../../ --charset=c
../../
|-- class
`-- src
    `-- pack1
        `-- TestJava.java

srcディレクトリにだけ、パッケージ用ディレクトリに作成したファイルがある状態になった。

パッケージを使用する際は、ソースの先頭に以下の記述を追加する。

package パッケージ名

記述例は、以下のようになる。

TestJava.java
package pack1;

class TestJava {

    public static void main(String[] args){

        System.out.println("test");

    }

}

このようにソースファイルの先頭に作成したパッケージ名を記述。今回は簡易なパッケージ名でしかテストをしないが、実際はjp/co/testcomp/cvのように階層を掘ってディレクトリを作成する。そして、それをソースに記述する際は、package jp.co.testcomp.cv;のように階層の区切りを「.」で表す。
例のようにパッケージ名は、インターネットドメイン名を逆にしたものから始めることを推奨されている。それは他社や他国のプログラムを利用する際にパッケージ名の衝突をしない為に、国や企業によって違いのあるドメイン名からパッケージ名をつけることが推奨されているからである。

では、再度実行してみる。

# javac -d ../class pack1/TestJava.java
# tree ../ --charset=c
../
|-- class
|   `-- pack1
|       `-- TestJava.class
`-- src
    `-- pack1
        `-- TestJava.java

classディレクトリにpack1ディレクトリごとファイルが作成された。
もしも、ソースファイルにpackageの記述がなかった場合は、classディレクトリに、pack1フォルダのない状態で、classファイルのみが作成される。

格納先の混乱を招かないように、ソースとクラスファイルの階層は合わせておくと良い。その為にも、packageの記述は忘れてはならない。(eclipseならエラー検知できるだろうけど)

ちなみに実行は、以下のように指定する。

# java -cp ../class pack1.TestJava
test

クラスパスで、classディレクトリは指定するが、パッケージからはpack1.TestJavaのように指定する。失敗例のような指定方法では、実行できない。

失敗例)

# java -cp ../class/pack1 TestJava
# java -cp  ../class TestJava
# java  ../class/pack1/TestJava

複数クラスのコンパイル&実行

まず以下のようにファイルを修正した。

TestJava.java
package pack1;

class TestJava {
    int i;
    char c;

    TestJava(){
        i = 1;
        c = 'A';
    }

    TestJava( int p_i, char p_c){
        i = p_i;
        c = p_c;
    }

    void go() {
        System.out.println("GO!");
        System.out.println("i =" + i + "\n" + "c = " + c );
    }

    public static void main(String[] args){

        System.out.println("test2");
        TestJava tj = new TestJava();
        TestJava tj_2 = new TestJava( 2, 'B');
        tj.go();
        tj_2.go();
    }

}

自身のインスタンスを生成し、コンストラクタで2パターン用意したものを出力。また、自身のメソッドでも出力するテストプログラムになっている。

もう一つファイルを作成した。

TestJava2.java
package pack1;

class TestJava2 {

    public static void main(String[] args){
        System.out.println("TestJava2");
        TestJava tj2 = new TestJava();
        TestJava tj2_2 = new TestJava2( 2, 'B');
        tj2.go();
        tj2_2.go();
    }

}

こちらは、TestJavaクラスからインスタンスを作り、実行する内容。

一旦作成していたclassファイルは削除して以下のような状態にする。

# tree ../../ --charset=c
../../
|-- class
|   `-- pack1
`-- src
    `-- pack1
        |-- TestJava.java
        `-- TestJava2.java

この2つのファイルをコンパイルするには以下の方法がある。
1.複数ファイルを同時にコンパイル。
javac -d ../../class TestJava.java TestJava2.java

2.呼び出し元のファイルをコンパイルしてから呼び出すファイルのコンパイル
この場合、TestJava2.javaは、TestJava.javaを使用するので、クラスパスも指定が必要。

# javac -d ../../class TestJava.java
# javac -d ../../class -cp ../../class TestJava2.java

実行結果は、こちら。

# java -cp ../../class pack1.TestJava2
TestJava2
GO!
i =1
c = A
GO!
i =2
c = B

以上。