Javaのクラスをグループ化するパッケージいまから始めるJava(10)

» 2003年10月11日 00時00分 公開
[平井玄@IT]

 クラスを使ったプログラムのメリットは、一度作ったクラスを再利用できることです。再利用できるのはプログラマ自身が作ったクラスだけではなく、他人が作ったクラスでも構いません。

 Javaを始めたばかりの皆さんは、「ほかの人が作ったクラスを利用する機会なんてあるのだろうか?」と思われるかもしれません。しかし、この連載の読者の皆さんはすでに他人が作ったクラスを再利用しています。そのクラスとは、String型のことです。

 String型はJavaというプログラム言語の仕様に組み込まれた極めて基本的なクラスですが、クラスである以上、String型というクラスの定義や、文字列を扱うためのメソッドを用意しなければなりません。String型のような基本のクラスは、Java言語を作った人たちによって作られました。

 String型のクラスの定義は、第1回「Java2 SDKで学習の準備」で少しふれたように「C:\Program Files\j2sdk1.4.1」にある「src.zip」という圧縮ファイルの中にあります。圧縮ファイルの中の「java/Lang」フォルダにある「String.java」というファイルです。圧縮ファイルの中からString.javaをデスクトップなどにドラッグ&ドロップし、エディタなどで開いてみてください。はじめにコメント文がありますが、以下のようなString型の定義があるはずです。これまでString型を何げなく使ってきましたが、この連載で定義したHTMLDocumentなどのクラス同様、メンバ変数やメソッドの定義が並んでいます。

public final class String
{
private char value[];
  private int count;
  public int length() {
    return count;
}
以降省略
   注:コメント文などを大幅に省略してあります。

 

 上記のメンバ変数やメソッドに付いているprivate、publicというキーワードの意味は前回「クラスのメンバに利用制限を付与するアクセス制御」で紹介したとおりです。簡単におさらいすると、privateを付けるとクラス内部の作業用になり、publicはこのクラスのインスタンスを生成したプログラムなど、外部から利用できる変数やメソッドになります。

 この連載において、これまでは単に文字列を操作するためのクラスとしてString型を使ってきましたが、String型の大きな特徴は一度設定した文字列は後から変更できない読み取り専用の型であることです。文字列はchar型配列のメンバ変数value[]に格納されます。また、value[]に格納された文字列の長さはint型のメンバ変数countに格納されます。

 ただし、value[]もcountもprivateに指定されていますので、クラスの外部からはアクセスできません。そこで、文字列の長さを調べるlength()のようなメソッドを使って間接的に文字列の長さを取得するわけです。別のいい方をすると、String型のソースコードを見たことのないプログラマは、length()がどのように文字列の長さを調べているのか分かりません。まさか内部にcountというメンバ変数があって、length()は単にcountの値を返しているだけとは気付かないわけです。

 Javaのようなオブジェクト指向言語ではクラスを継承させることで元のクラスの機能を拡張できますが、サブクラスを定義するプログラマでさえ、スーパークラスがどのように定義されているのか、詳細を知る必要はありません。クラスという道具を使いこなすには、それぞれのクラスがどのような機能を持っているかを理解すればよく、その道具はどのようにできているのかは知らなくてよいのです。

 このことをオブジェクト指向の世界では「カプセル化」といいます。クラスの内部構造は隠してしまって、プログラマは公開されたメンバ変数やメソッドだけを使ってください、ということです。従って、何かのクラスについて知りたいプログラマは、クラスの定義ではなく、クラスのpublicなメンバ変数とメソッドとしてどんなものが用意されているのか調べればよいのです。

 ただし、カプセル化されているのだから、クラスの定義なんて知らなくていい、とは思わないでください。確かにプログラムを作るうえではクラスの定義を知っている必要はありません。しかし、Javaを作った人々が書いたソースコードを眺めることは、それだけで勉強になります。ソースコードを眺めることをきっかけに、初心者から中級者、中級者から上級者にステップアップすることもあると思います。クラスの説明をどんなに読んでも分からなかったことがソースコードを読んだら分かったということもよくあることです。

パッケージとは?

 Javaが提供しているクラスや他人が作ったクラスを再利用する場合、クラスの名前に注意しなければなりません。String型のような基本的なクラス名と重複するようなクラスは作った方に責任がありますが、この連載で作成したHTMLDocumentのように、Javaの言語仕様には含まれないけれど、誰が作ってもいかにも同じ名前が付きそうなクラスはいくつもあります。そこでjavaには「パッケージ」という考え方があります。

 説明の前に実際にパッケージを作ってみましょう。

パッケージ宣言付きのHTMLDocument
package jp.co.atmarkit.java;
public class HTMLDocument {
  private String source;
  public void setSource( String html ) {
    if ( html.indexOf("<html>") == 0 )
      source = html;
  }
  public String getSource() {
    return source;
  }
  public void showPlainText() {
    // 省略
  }
}

  パッケージの保存からコンパイルまでの手順は以下のとおりです。

(1) HTMLDocument .javaとして、C:\Documents and Settings\<ユーザー名>\My Documents\My Java Applications\jp\co\atmarkit\javaに保存する
(2) C:\Documents and Settings\<ユーザー名>\My Documents\My Java Applications\jp\co\atmarkit\javaというフォルダを作る
(3) C:\Documents and Settings\<ユーザー名>\My Documents\My Java Applications\jp\co\atmarkit\javaに移動して、HTMLDocument .javaをコンパイルする
C:\DOCUME~1\MYDOCU~1\MYJAVA~1>\JP\CO\ATMARKIT\
JAVA>javac HTMLDocument.java

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>\JP\CO\
ATMARKIT\JAVA>

 次に、パッケージとして保存したHTMLDocumentを利用するプログラムを作成します。

HTMLDocumentを利用するプログラム
import jp.co.atmarkit.java.HTMLDocument;
public class PackageTest {
  public static void main( String args[] ) {
    HTMLDocument doc = new HTMLDocument();

    doc.setSource("<html>TEXT</html>");
  }
}

 こちらのファイルはいつもどおりC:\Documents and Settings\<ユーザー名>\My Documents\My Java Applications\にPackageTest.javaの名前で保存してからコンパイルします。

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>\JP\CO\ATMARKIT\
JAVA>javac PackageTest.java

C:\DOCUME~1\MYDOCU~1\MYJAVA~1>\JP\CO\
ATMARKIT\JAVA>

 何かを実行するプログラムではありませんので何も起こりませんが、PackageTest.javaにはHTMLDocumentの定義が含まれないのに、HTMLDocumentを利用できたことが分かります。

 それでは、はじめから順に説明していきます。

package jp.co.atmarkit.java;
public class HTMLDocument {

 HTMLDocumentの定義を含むHTMLDocument.javaには、いままでにはない「package jp.co.atmarkit.java」という文があります。「jp.co.atmarkit.java」全体がパッケージの名前になります。続いてクラスの宣言をしています。このとき、クラスはpublicに指定されていますので、「package jp.co.atmarkit.java」というパッケージに所属する「HTMLDocument」というクラスという意味になります。

 次にPackageTest.javaとして保存したファイルをもう一度見てみましょう。

import jp.co.atmarkit.java.HTMLDocument;
public class PackageTest {
  public static void main( String args[] ) {
    HTMLDocument doc = new HTMLDocument();

 冒頭にはこれもいままでなかった「import jp.co.atmarkit.java.HTMLDocument;」という文があります。4行目に出てくるHTMLDocumentのクラス定義はPackageTest.javaに含まれませんが、コンパイルエラーになりません。はじめのimport文によって、コンパイラはHTMLDocumentが「jp\co\atmarkit\java\」にある「HTMLDocument.class」のことだと分かるからです。つまり、パッケージの名前とディレクトリ構造を一致させることで、コンパイラが目的の.classファイルを見つけ出せるようにしているわけです。また、クラス名が同じでも、パッケージ名が異なれば別のディレクトリに保存されることになりますので、同じクラス名でも共存できるわけです。

パッケージ名とクラス名

 String型の話に戻りましょう。String.javaのファイルに「package java.lang;」という文があります。

/*
 * @(#)String.java 1.159 03/01/23
 *
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.lang;

 つまり、String型クラスも「java.lang」というパッケージに所属するクラスなのです。しかし、これまでの連載では「import java.lang.String」などとしないでもString型を利用できました。いったいどうしてなのでしょうか?

 実はjava.langというパッケージにはString型や「System.out.println(…)」で登場したSystemのように、Javaの言語仕様そのものといえるクラスが所属しています。あまりにも基本的なので、わざわざ「import java.lang.String」などとしないでも利用できるようになっているのです。

 パッケージとクラスの関係では、もう1つ重要なことがあります。それは、正式なクラス名は常にパッケージとの組み合わせで表現されるということです。ソースファイルの中でこそ「String」のようにクラス名単独で登場しますが、あくまでも「java.lang.String」が正式なクラス名です。Java用語では、Stringのような省略形を「単純名(simple name)」、「java.lang.String」のような形式を「完全修飾名(fully qualified name)」と呼びます。

 ちょっと分かりにくいので実例を示しましょう。

単純名のサンプル
public class SimpleNameSample {
  public static void main( String args[] ) {
    String s = "Simple Name Sample";
  }
}

 単純名を使った上記のプログラムは、以下のように完全修飾名を使っても記述できます。

完全修飾名のサンプル
public class FullyQualifiedNameSample {
  public static void main( String args[] ) {
    java.lang.String s = "Fully Qualified Name Sample";
  }
}

 クラス名を名前だとすると、パッケージ名は名字に相当すると考えればよいでしょう。パッケージはディレクトリ構造で管理されるので同じコンピュータ内で競合することはありません。さらに、コンパイラそのものは常にクラス名を完全修飾名で扱いますので、たとえクラス名が同じになることがあっても、パッケージ名は異なりますから、完全修飾名としては区別できるわけです。

 コンパイラがクラス名を完全修飾名で扱っていることは、コンパイラが生成する.classファイルをエディタで開くと分かります。以下は「単純名のサンプル」をコンパイルしたSimpleNameSample.classファイルです。

…………………………<init>……()V……Code……LineNumberTable
……main…… ([Ljava/lang/String;)V…SourceFile……
SimplenameSample.java……Simple Name Sample……
SimpleNameSample……java/lang/Object !………………

 単純名で記述したはずですが、.classファイルには「java/lang/String」という文字があるのが分かります。一方、「完全修飾名のサンプル」をコンパイルしたFullyQualifiedNameSample.classファイルは以下のようになります。

…………………………<init>……()V……Code……LineNumberTable
……main……([Ljava/lang/String;)V…SourceFile……
 FullyQualifiedNameSample .java……Fully Qualified Name Sample
……FullyQualifiedNameSample…… java/lang/Object !………………

 クラス名と文字列部分は違いますが、バイナリ部分や「java/lang/String」の部分は同じになっていることがわかります。コンパイラ内部で、クラス名は常に完全修飾名で扱われます。しかし、人間にとって完全修飾名は冗長です。そこでクラス名が重複しない限り、importだけで単純名で表記できるようにしているわけです。

 なお、import文のクラス名は省略できます。例えば、java.utilに含まれるComparator、ArrayListというクラスを利用したいときに、以下のように記述できます。

import java.util.*;

 アスタリスクはクラス名の代わりに使えます。この場合は、「java.util」というパッケージに所属するクラスすべてを利用する、という意味になります。従って、import文を省略できる「java.lang」パッケージについては、「import java.lang.*」という文が省略されている、とも考えられます。

パッケージ名の命名方法

 パッケージが完全修飾名で表したときのクラス名の重複を防ぐ方法であることは分かったと思います。では、パッケージ名の重複はどのように防ぐのでしょうか? Javaで何かを開発しようと思ったら、1人の開発者が1台の閉じたコンピュータで作業するというわけにはいきません。Javaに含まれるさまざまなパッケージや、他人の作ったパッケージを購入して使う場合もあります。このとき、それぞれが勝手なパッケージ名を付けてしまったら、クラス名の重複を防ぐという機能を果たせなくなります。

 そこでJavaでは、パッケージ名として所属企業のドメイン名をトップレベルドメイン側から並べ替えてパッケージ名にする方法が推奨されています。はじめに紹介したsrc.zipの中身に、例えば「com/sun/corba」というディレクトリがありますが、これは「corba.sun.com」というドメイン名を逆さまにしたものです。今回HTMLDocumentのパッケージ名にした「jp.co.atmarkit.java」は、「java. atmarkit. co.jp」というドメイン名を逆さまにしたものです。こうすれば、たとえHTMLDocumentというクラス名を使うほかのパッケージがあったとしても、完全修飾名では区別できるわけです。皆さんが自作のパッケージを作る場合は、自社のドメイン名を使うとよいでしょう。

 次回は、String型のソースコードを詳しく見ながら、クラスの理解をさらに深めます。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。