【Java】クラスパスについて -“クラスパスを通す”とは-

Java

クラスパスとは

Javaの開発を行っていると、「クラスパス」という用語や「クラスパスを通す」という表現が出てくることがあります。クラスパスとはすなわち「パッケージ構成に沿ってディレクトリに配置されたクラスファイルを検索する際の起点となるディレクトリの候補リスト」であると言えます。

クラスパスに設定されたディレクトリ(またはアーカイブファイル)のリストを先頭から使用し、そこからパッケージと同じ階層のディレクトリを辿った先にあるクラスファイルを、クラスローダが検出するために使用されます。

クラスパスとは
クラスファイル検索の起点となるディレクトリ(およびアーカイブファイル ※後述 のリスト

クラスパスの必要性

パッケージとディレクトリ構成

クラスパスを知るためには、まずJavaのパッケージついて簡単に理解しておく必要があります。

Javaのパッケージについて詳細はこちらです。

例えば C:\Users\java\src ディレクトリでコマンドプロンプトを開いている場合、次のような com.example パッケージの Main クラスは、com\example ディレクトリに配置する必要があります。

package com.example;

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

javacコマンドはデフォルトでソースファイルと同じ場所にクラスファイルを作成するため、

# ① ソースファイルまでのパスを指定してコンパイル
C:\Users\java\src> javac com\example\Main.java

# ② 完全修飾クラス名を指定して実行
C:\Users\java\src> java com.example.Main
Hello World

という2つの手順を踏むことで、Javaプログラムを正常に実行することができます。

しかし、このようにクラスファイルの出力先を変更した場合、javaコマンドによる実行時に問題が発生するようになってしまいます。これは、クラスファイルの出力先を変更したことにより、実行コマンド java com.example.Main に対し、C:\Users\java\src\com\example\Main.class が存在しない状態となっているためです。

# ① クラスファイルの出力先ディレクトリを指定してコンパイル
C:\Users\java\src> javac -d ..\bin com\example\Main.java

# ② 完全修飾クラス名と一致するディレクトリのクラスファイルが見つからないためエラー
C:\Users\java\src> java com.example.Main
エラー: メイン・クラスcom.example.Mainを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: com.example.Main

最も簡単な方法は、cd ../bin でクラスファイル出力先ディレクトリのルートへ移動してからjavaコマンドを実行することです。実際にこの方法で正常にプログラムを実行できるようになります。しかしこのやり方は、毎回ディレクトリを移動するのが単に面倒なだけでなく、実行時のカレントディレクトリに依存する状態となってしまっているため避けるべきです。

対応するディレクトリの起点

そこで、このような時にクラスパスが役に立ちます。クラスパスとは「クラスファイルを検索するディレクトリ」であり、クラスパスに設定したディレクトリを起点に、パッケージに対応するディレクトリに配置されたクラスが走査されるようになります。

上記の例の場合、クラスパスに C:\Users\java\bin ディレクトリを設定しておくことで、java com.example.Main と実行した時に、今いるディレクトリに関係なく、C:\Users\java\bin を起点に、com.example パッケージに対応する com\example ディレクトリが検索され、C:\Users\java\bin\com\example\Main.class が実行対象になる、という流れです。

このように、パッケージと同じディレクトリ構成を検索する際、どのディレクトリを起点とするべきかを指定するためのパスがクラスパスであり、あるディレクトリをクラスパスに設定することを、しばしば「クラスパスを通す」と表現します。この考え方は、環境変数 PATH と非常によく似ています。

環境変数 PATH についての理解があると、クラスパスの概念も受け入れやすくなります。

パッケージの注意点1の補足

前述 したリンク先のページ内では、パッケージの注意点として 階層を合わせる必要がある としていますが、従って厳密にはこの説明ではまだ不足しており「“クラスパスに設定したディレクトリを起点として”、パッケージとクラスファイルの配置ディレクトリとの階層を合わせる必要がある」というのがより正確な表現となります。つまり上記ページでは、対象クラスが com.example.Main なら、com\example\Main.class とする必要がある、とは説明しているものの、では com ディレクトリはどこに配置すべきかというところまでは言及していないということです。

パッケージの最上位階層(ここでは com ディレクトリ)は必ずクラスパス上に配置する必要があり、逆に言えばパッケージの最上位階層(ここでは com ディレクトリ)が配置されたディレクトリは必ずクラスパスに設定する必要があります。これが無ければ、クラスローダは起点となるディレクトリが分からず、クラスファイルを1つ検索する度にルート( C:/ )からコンピュータ上のあらゆるディレクトリを全て見に行かなくてはならなくなってしまいます。

従って、javaコマンドによるプログラム実行時に、クラスパスの設定は必須となります。ただ、これまでの例では必ずしもクラスパスの設定を行ってきませんでした。これは、明示的な指定がない場合、クラスパスにはカレントディレクトリが設定されるためです。

クラス・パス・オプションが使用されておらず、classpathが設定されていない場合、ユーザー・クラス・パスは現在のディレクトリ(.)で構成されます。

初めの例では、明示的にクラスパスの設定を行わなかったために、C:\Users\java\src ディレクトリがクラスパスに設定され、C:\Users\java\src\com\example\Main.class が検索されて java com.example.Main が正常に実行されたということになります。

クラスパスの設定方法

クラスパスの設定には次の二つの方法があります。

クラスパスの設定方法

  • javaコマンドのオプションを使用する方法(推奨)
  • 環境変数 CLASSPATH を使用する方法

プログラムの実行単位で個別に設定できるため、通常はjavaコマンドのオプションを使用する方法が推奨されます。

クラスパスの設定は、SDK ツールを呼び出すときに -classpath オプションを付ける方法 (この方法が望ましい) か、CLASSPATH 環境変数を設定する方法により行います。 -classpath オプションを推奨しているのは、各アプリケーションに対して個別に設定でき、ほかのアプリケーションに影響したり、ほかのアプリケーションからこの値が変更されたりすることがないためです。

オプションを使用する方法

この方法では、javaコマンド実行時にオプションの引数としてクラスパスを直接指定します。

# -cp に続けてクラスパスを指定する。
$ java -cp C:\Users\java\bin com.example.Main

大抵の場合、-cp オプションが使用されますが、キーワードとしては -cp の他、--class-path-classpath も用意されています。そのため以下のどれを使用してもクラスパスの指定が可能です。挙動に違いはありません。

  • $ java --class-path C:\Users\java\bin com.example.Main
  • $ java -classpath C:\Users\java\bin com.example.Main
  • $ java -cp C:\Users\java\bin com.example.Main

また、次に説明する環境変数 CLASSPATH も同時に設定されている場合、オプションで指定したクラスパスの設定が優先されます。

classpathを指定すると、CLASSPATH環境変数の設定がオーバーライドされます。

環境変数を使用する方法

推奨は -cp オプションを使用する方法ですが、アプリケーションや環境ごとにクラスパスを変更したり、その影響を考慮したりする必要がない場合、環境変数 CLASSPATH を使用することが可能です。これにより、毎回オプションを指定する必要がなくなり、短いコマンド入力でプログラムを実行することができます。

環境変数の概要と設定方法についてはこちらを参照してください。
# 環境変数 CLASSPATH にクラスパスを設定
$ set CLASSPATH=C:\Users\java\bin

# 設定した環境変数の値を確認
$ echo %CLASSPATH%
C:\Users\java\bin

# -cp オプションを指定せず実行
$ java com.example.Main
Hello World

複数のパスを設定する場合

クラスパスの活用場面

ディレクトリの共有

クラスパスを通す例として、bin ディレクトリをここまで例に挙げてきました。これは初めに、ソースファイルとクラスファイルを異なるディレクトリに分けて配置することで、全体の見通しを良くするためであると説明しました。ただしこれはあくまで、いくつかある理由のうちの一つに過ぎません。

ディレクトリを分けるその他の理由としては、チーム開発時のソース共有や、外部ライブラリの利用時に役立つということ等が挙げられます。例えば自分の開発したクラスのまとまりをチームメンバに共有したり、逆に配布されている一連のクラス群を利用したりする際にも、ソースファイルとクラスファイルを分けておくことは有用です。

例として、チーム開発で自分が担当した機能をメンバに共有する場面を考えてみます。この時、メンバにとって必要なのはプログラム実行に必要なクラスファイルだけです。特にクラス数が多い場合など、共有する資源に不要なソースファイルが含まれている状態は避けるべきです。このような場合にも、ディレクトリを分けることが役に立ちます。開発に必要な自分だけが参照できれば良いソースファイルと分けておくことで、実行に必要なクラスファイルだけが含まれたディレクトリを容易にチーム内で連携することができます。

By doing this, you can give the classes directory to other programmers without revealing your sources.
こうすることで、ソースを公開することなくクラスディレクトリを他のプログラマに渡すことができます。

また、反対にチーム内の他のプログラマが開発したクラス群を自分が利用したい場合、メンバから貰ったディレクトリの中身を自分のワークスペースにコピーしたりする必要はなく、単にそこへクラスパスを通せばそれで済みます。前述の通りクラスパスには複数のディレクトリを追加することができるため、下の例では、-cp C:\Users\java\bin;C:\Users\java\Downloads\member2pjt\bin とすることで、実際のディレクトリは異なっていても、Javaプログラム上は、com.example パッケージ、com.example.util パッケージと、それぞれ認識できるようになります。

C:
└ Users
   └ java
      ├ src
      │ └ com
      │    └ example
      │       └ Main.java
      ├ bin
      │ └ com
      │    └ example
      │       └ Main.class
      └ Downloads
         └ member2pjt   ← ダウンロードした物を自分の bin にコピーしたりはせず、
            └ bin       ← このディレクトリをクラスパスに追加すれば良い。
               └ com
                  └ example
                     └ util
                        └ Calculator.class
  • -cp C:\Users\java\bin;C:\Users\java\Downloads\member2pjt\bin
    • ディレクトリ構成
      • C:\Users\java\bin\com\example\Main.class
      • C:\Users\java\Downloads\member2pjt\bin\com\example\util\Calculator.class
    • 完全修飾クラス名
      • com.example.Main
      • com.example.util.Calculator

Source の方をダウンロードして解凍すると、中身は src\main\java\org\apache\commons\lang3\... のような構成になっており、様々なソースファイル(.java)が格納されています。

一方 Binaries はダウンロードして解凍すると、commons-lang3-3.17.0.jar のようなファイルが含まれています。JARファイルとは、ZIPファイルの一種に過ぎないため、拡張子を .zip に変更することで、通常のZIPファイルと同様に解凍することができます。

JARファイルは普及しているZIPファイル形式に基づくファイル形式で、多数のファイルを1つにまとめるために使用されます。 JAR ファイルは、基本的にはオプションのMETA-INFディレクトリを格納するZIPファイルです。

このJARファイルを展開すると、中身は org\apache\commons\lang3\... のような構成になっており、様々なクラスファイル(.class)が格納されています。

C:
└ Users
   └ java
      ├ src
      │ └ com
      │    └ example
      │       └ Main.java
      ├ bin
      │ └ com
      │    └ example
      │       └ Main.class
      └ Downloads
         └ commons-lang3-3.17.0-bin
            └ commons-lang3-3.17.0
               ├ commons-lang3-3.17.0.jar
               │ ↓拡張子を書き換え(.jar→.zip)
               ├ commons-lang3-3.17.0.zip
               │ ↓すべて展開...
               └ commons-lang3-3.17.0   ← このディレクトリをクラスパスに追加すれば良い。
                  └ org
                     └ apache
                        └ commons
                           └ lang3
                              └ ...(.class)

従って、C:\Users\java\Downloads\commons-lang3-3.17.0-bin\commons-lang3-3.17.0\commons-lang3-3.17.0 へクラスパスを通すことで、そこからパッケージを辿れるようになるため、例えば完全修飾クラス名 org.apache.commons.lang3.StringUtils のような呼び出しが可能となります。

アーカイブファイルの指定

ただし、ここまでに示したJARファイルを解凍してクラスパスに追加する方法は、あまり一般的ではありません。クラスパスには、環境変数PATHのようにディレクトリパスを追加していくと説明してきましたが、実際にはディレクトリだけでなくアーカイブファイルも設定することができるためです。

開発チーム内でディレクトリを共有する例と、Apache Commons Lang のライブラリを利用する例を2つ挙げました。自分の開発環境となるコンピュータにこれらのディレクトリを配置するにあたり、開発チーム内で共有する際は、ディレクトリをそのままではなく、一度ZIPファイルにまとめてメンバ間で渡しあうことが想定されます。そして Apache Commons Lang では前述の通り、JARファイルの形式でディレクトリをアーカイブしたものが配布されています。

これらにクラスパスを通す際、わざわざ解凍してからそのディレクトリを追加せずとも、クラスパスはディレクトリの他、JARとZIPの形式にそれぞれ対応しているため、そのままファイルパスを追加することができます。

クラス・ファイルを検索するディレクトリ、JARファイルおよびZIPアーカイブのリストを指定します。

そのため、チーム内で共有されたZIPファイルや、Apache Commons Lang からダウンロードしたJARファイルは、いずれも展開することなく、次のように直接クラスパスに追加することが可能です。

$ java -cp C:\Users\java\bin;C:\Users\java\Downloads\member2pjt\bin.zip;C:\Users\java\Downloads\commons-lang3-3.17.0-bin\commons-lang3-3.17.0\commons-lang3-3.17.0.jar com.example.Main

そのため一般にクラスパスを通すといえば、通常はJARファイルのパスを追加することを指します。後述 するEclipseでの設定においても、基本的にはJARファイルの追加手順が多く用いられます。

その他の活用例

このクラスパスの仕組みは、JUnitなどのテストコードからテスト対象クラスを呼び出す際にも有効活用できます。

workspace
└ src
   ├ main
   │ └ java   ← workspace\src\main\java にクラスパスを通す。
   │    └ com
   │       └ example
   │          └ Calculator.java
   └ test
      └ java   ← workspace\src\test\java にクラスパスを通す。
         └ com
            └ example
               └ CalculatorTest.java

これにより、実際にクラスファイルが配置されているディレクトリは異なりつつ、Javaプログラムが認識するパッケージは同じになるため、テストクラスから見た対象クラスは同じパッケージに属するクラスとなり、インポート文の記述が不要になります。

package com.example; // 同じパッケージのためテスト対象クラスのインポートは不要

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

class CalculatorTest {

    // テスト対象クラス(com.example.Calculator)
    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
        int expected = 5;
        int actual = calculator.add(2, 3);
        assertEquals(expected, actual);
    }
}

Eclipseの場合

クラスパスの設定方法 では、-cp オプションや環境変数を使用する方法を挙げましたが、Eclipseで開発を行っている場合、このことを知っていてもそのまま活用することはできません。EclipseにはEclipseの設定の仕方があるため、裏ではこのようなことが行われているという理解は持ちつつ、設定画面からクラスパスを追加する手順も別に知っておく必要があります。

Eclipseでクラスパスの設定を行うための詳細手順はこちらです。

クラスパスの要点

  • クラスパスとは
    • クラスファイル検索の起点となる ディレクトリ・ZIP・JAR のリスト
  • クラスパスの設定方法
    • javaコマンドの-cpオプションでパスを指定(推奨)
    • 環境変数 CLASSPATH にパスを設定