Java/文法/パッケージ

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Wikipedia
Wikipedia
ウィキペディアJavaのパッケージの記事があります。

Javaのパッケージは、クラスを管理しやすくするためにあります。 パッケージを用いることで名前が同じクラスを作ることができるようになります。 C++経験がある人は、C++にある名前空間(namespace)のようなものだと思って下さい。

なお、WikipediaにはJavaのパッケージの記事があります。

パッケージ宣言の仕方[編集]

Javaでのパッケージ宣言は、Javaソースコードの先頭で以下のように記述します。

package パッケージ名.サブパッケージ名.サブサブパッケージ名;

パッケージ名は、パッケージ宣言するときにドット(.)で区切ることでサブパッケージをいくつでも作ることができます。 では実際にJavaソースコードでパッケージ宣言をしてみましょう。

package org.wikibooks.ja.newpackage;

public class PackageDemo {
  public static void main(String[] args){
    System.out.println("パッケージテスト");
  }
}

このJavaソースファイルをPackageDemo.javaという名前で保存します。 Javaソースファイルを保存するとき、保存するディレクトリを指定して下さい。 (ここではWindowsを例に説明します。LinuxやUNIXなどのOSではバックスラッシュ(\)をスラッシュ(/)に置き換えてルートディレクトリも変えて下さい。)

ここでは「ディレクトリ」という言葉を使っていますが、これはWindowsでは「フォルダ」と同じ意味です。

例えば、Javaプログラムを置くディレクトリが C:\wikibooks\java\src であるとします。 package宣言で指定したパッケージ名はorg.wikibooks.ja.newpackageですので、 パッケージのディレクトリはorg\wikibooks\ja\newpackageとなります。実際にはパッケージ名とディレクトリ名を一致させる必要はありませんが、Javaでは、わかりやすさのためにパッケージに合わせてディレクトリを作成してそこにJavaソースファイルを置くことが慣習になっています。

コンパイルと実行[編集]

すると絶対パスでPackage.javaを置くディレクトリは

C:\wikibooks\java\src\org\wikibooks\ja\newpackage

となります。

このJavaソースファイルをコマンドラインで実際にコンパイルするには以下のようにします。

cd C:\wikibooks\java\src
javac org\wikibooks\ja\newpackage\PackageDemo.java

すると、クラスファイルがディレクトリ C:\wikibooks\java\src\org\wikibooks\ja\newpackage にできます。 このクラスを実際に実行するには以下のようにします。

java -classpath C:\wikibooks\java\src org.wikibooks.ja.newpackage.PackageDemo

これで「パッケージテスト」という文字が表示されます。 ここでjavaコマンドのオプションに-classpathを指定していることに気を付けて下さい。これはJavaクラスファイルがカレントディレクトリC:\wikibooks\java\srcから見て相対的にorg\wikibooks\ja\newpackageにないときに、明示的にクラスファイルがある相対パスを指定しています。カレントディレクトリがC:\wikibooks\java\srcでないときにこのオプションを指定しないと、javaコマンドはクラスファイルを見つけることができず、エラーとなってしまいます。 カレントディレクトリがC:\wikibooks\java\srcの場合は、以下のように指定して下さい。

java -classpath . org.wikibooks.ja.newpackage.PackageDemo

-classpathオプションは短縮して-cpと書くこともできます。 環境変数CLASSPATHを使用する方法がありますが、Javaの特性を損なう環境依存を引き起こすため、お勧めはしません。なるべく-classpathオプションを使用しましょう。 これでパッケージの使用方法を説明しました。 しかし、このままではパッケージの利点がわかりにくいかもしれません。 そこで、もう一つJavaソースファイルを作ってみましょう。

異なるディレクトリに作成したJavaソースファイルを参照[編集]

今度は、異なるディレクトリにファイルを置きます。 ここでは、C:\wikibooks\java\src\org\wikibooks\ja\newpackage に subとsub2という名前のディレクトリを作成して下さい。sub という名前のディレクトリを作成します。そのsubというディレクトリに、新たに、PackageDemo.javaというJavaソースファイルを新規作成して下さい。先ほど作成したJavaソースファイルと全く同じ名前ですが、違うディレクトリにファイルを置いているため、名前衝突が起こりません。もう一つ、同じディレクトリにOtherClass.javaというJavaソースファイルを新規作成して下さい。sub2というディレクトリには OtherPackageClass.javaという名前のJavaソースファイルを新規作成して下さい。 すると、以下のようなディレクトリ構成になります。

C:\wikibooks\java\src
      |
      ---> org\wikibooks\ja\newpackage\  
            |
            ---> PackageDemo.java
            |
            ---> sub\
            |     |
            |     ---> PackageDemo.java
            |     |
            |     ---> OtherClass.java
            |     
            ---> sub2\
                  |
                  ---> OtherPackageClass.java

subディレクトリで作成したPackageDemo.javaは以下のように書いて下さい。

package org.wikibooks.ja.newpackage.sub;

public class PackageDemo {
  public static void main(String[] args){
    System.out.println("subパッケージテスト");
    //同一パッケージにある他のクラスを呼び出し、
     //インスタンスを生成する。
     OtherClass otherClass = new OtherClass();
    //生成したインスタンスを用いてOtherClassのtest()メソッドを実行する。
    otherClass.test();
  }
}

OtherClass.javaは以下のように書いて下さい。

package org.wikibooks.ja.newpackage.sub;
//異なるパッケージのクラスを呼び出すためにimport宣言する。 
import org.wikibooks.ja.newpackage.sub2.OtherPackageClass;

public class OtherClass {
  public void test(){
    System.out.println("OtherClass");
    //異なるパッケージのクラスを呼び出し、インスタンスを生成する。
    OtherPackageClass otherPackageClass = new OtherPackageClass();
    //生成したインスタンスを用いてOtherPackageClassのdo()メソッドを実行する。     
    otherPackageClass.test();
  }
}

OtherPackageClass.javaは以下のように書いて下さい。

package org.wikibooks.ja.newpackage.sub2;

public class OtherPackageClass {
  public void test(){
    System.out.println("OtherPackageClass");
  }
}

コンパイルと実行[編集]

これらのJavaソースファイルは以下のようにしてコンパイルします。

> cd C:\wikibooks\java\src
> javac org\wikibooks\ja\newpackage\sub\*.java
> javac -classpath C:\wikibooks\java\src org\wikibooks\ja\newpackage\sub2\*.java

ここで、*(アスタリスク)はワイルドカードを意味します。ワイルドカードとは、任意の文字列のことであり、喩えるならトランプのポーカーでいうジョーカーの札にようなもにです。この例ではsubディレクトリとsub2.javaで終わるJavaソースファイルをまとめてコンパイルするということを意味します。 すると、三つのクラスファイルがディレクトリ C:\wikibooks\java\src\org\wikibooks\ja\newpackage\sub と C:\wikibooks\java\src\org\wikibooks\ja\newpackage\sub2 にできます。 このクラスを実際に実行するには以下のようにします。

> java -classpath C:\wikibooks\java\src org.wikibooks.ja.newpackage.sub.PackageDemo

すると、以下のように表示されます。

subパッケージテスト
OtherClass
OtherPackageClass

この例では、subパッケージにあるPackageDemoクラスが、同じパッケージ内のOtherClassクラスを呼び出しています。 つぎに、OtherClassを実行するときに、OtherClassは他のパッケージsub2にあるOtherPackageClassにアクセスしています。OtherPackageClassはsubとは異なるパッケージ、sub2という名前のパッケージであるため、import宣言を用いて目地的にパッケージを指定する必要があります。次にimport宣言について説明します。

import宣言で自作したパッケージのクラスを呼び出す[編集]

subというパッケージにあるOtherClassは異なるパッケージsub2にあるOtherPackageClassにアクセスしています。そのために、明示的にパッケージ名を指定しなければOtherPackageClassを使用することはできません。 OtherClass.javaのソースコードを見てみましょう。OtherClass.javaにはこのようなimport宣言があります。

//異なるパッケージのクラスを呼び出すためにimport宣言する。 
import org.wikibooks.ja.newpackage.sub2.OtherPackageClass;

これはorg.wikibooks.ja.newpackage.sub2パッケージにあるOtherPackageClassクラスを参照していることを意味します。C言語の#include宣言に似ていますが、若干違います。import宣言を使わなくてもOtherPackageClassを呼び出す事は可能です。そこがC言語とは大きく異なります。その方法は以下のようにします。

完全修飾名[編集]

OtherClass.javaを以下のように書き換えます。

package org.wikibooks.ja.newpackage.sub;
 
public class OtherClass {
  public void test(){
    System.out.println("OtherClass");
    //異なるパッケージのクラスを呼び出し、インスタンスを生成する。
    org.wikibooks.ja.newpackage.sub2.OtherPackageClass 
     otherPackageClass 
       = new org.wikibooks.ja.newpackage.sub2.OtherPackageClass();
    //生成したインスタンスを用いてOtherPackageClassのtest()メソッドを実行する。     
    otherPackageClass.do();
  }
}

これは以前に書いたOtherClass.javaと等価です。どちらも同じですが、こちらはソースコードが読みづらいですね。これは好みの問題ともいえますが、急いでいるときにこの手を使うと良いでしょう。好みの問題以外にも、どうしてもこの完全修飾名をつかわなければならないケースがあります。例えば、PackageDemoクラスのように、パッケージ名は異なるけれども同じ名前のクラスがありますね。こういうときに、この完全修飾名は大いに有効な手段として使うことができます。import宣言で宣言できるクラスは、同じ名前のクラスであってはいけないのです。ふたつのPackageFDemoクラスを一つのJavaソースファイル内で同時にimport宣言するとエラーになってしまいます。そのため。片方、または両方をこのように長い完全修飾名で呼び出すことで、クラス名の衝突を回避することができます。

名前衝突の回避[編集]

このような名前が衝突する恐れがある組み合わせはJava標準APIにも実際に存在します。その一例がjava.util.Dateとjava.sql.Dateです。これらのクラスは同時にimportすることはできません。完全修飾名を使う必要があります。 ただし、通常はこんなことをする必要はありません。委譲による「ラッパークラス」の自作や、デザインパターンの一種「Facade パターン」などの手法を使って、別のクラスを作成することで名前の衝突を防ぐことが可能です。java.util.Dateとjava.sql.Dateについては、よほどのことでもない限り、同時に使うことは滅多にないでしょう。頻繁に完全修飾名を使うようになることがあるなら、クラス名のネーミングや設計に問題があると思った方が良いでしょう。デザインパターンについてよく勉強すれば、こういうことをしなくても済むようになります。