Deep Side of Java〜Java 言語再入門 第2回 〜 Java 文法を中心に

組み込みクラスライブラリ・パッケージ

java.util.*






組み込みクラスライブラリ・パッケージ

java.util.*

java.util パッケージには、大体2通りの内容がある。1つは stdlib.h に相当するようなユーティリティであり、もう1つはリストやハッシュテーブルのような便利なデータ構造が定義されている。まず、ユーティリティについて解説するが、これらはすべて「国際化」を前提に設計されていることに注意されたい。つまり、「Locale」(どの国の慣習に従うか)を設定でき、設定された Locale に従って出力をすることができる。

java.util.Locale

その Java VM(インタプリタ)がどういうロケールで動作するか、ということの基本データは、System.getProperties() メソッドで取得できる「システムプロパティ」の中にある。このシステムプロパティを一覧してみると、

user.langugage : ja
file.encoding : EUC-JP-LINUX
user.region : JP

のプロパティがある。デフォルトで読み書きするコーディングは file.encoding に書かれているのである。また、使用言語は日本語であり、地域も日本である、という風に基本的なロケールが設定されているのがわかる。実際にはコンパイラが出力するメッセージが日本語であるのも、このシステムプロパティが日本を示すものだからこそのことである。ちなみに、ロケールは言語とサブ項目の地域によって指定される。この時に両者をつなげて表示する記法をする時がある。たとえば「ja_JP」のような要領である。たとえば Linux などの UNIX を使っていると、環境変数 LANG にこういう値がセットされているが、これもロケールを示し、Java クラスライブラリと共通する指定方法を使っている。

このロケールを使うと、大規模な応用としては、ロケールに依存したアプリケーションの表示メッセージの国際化などが可能である。このためのライブラリとして java.text パッケージがあり、また、java.util.ResourceBundle クラスと協調して国際化を実現している。

java.util.Date

この Date クラスは、いわゆる UNIX エポック(1970年1月1日00:00:00 GMTからの経過秒数)を中心となるデータとして表現する、日時を扱うクラスである。一応ミリ秒単位まで扱うことができる。C言語標準ライブラリにあるものと同じような雰囲気で使うことができる。

Date()
コンストラクタ。現在の時間を取得して Date オブジェクトを作る。
Date(int year, int month, int date)
コンストラクタ。引数の日付によって Date オブジェクトを作る。
Date(int year, int month, int date, int hour, int min, int sec)
コンストラクタ。引数の日時によって Date オブジェクトを作る。
Date( String s )
コンストラクタ。文字表現の日時によって Date オブジェクトを作る。
Date( long time )
エポックからの経過秒数によって Date オブジェクトを作る。
int getYear(), void setMonth(int) など
それぞれのフィールドを読み書きするアクセサ。セッタの場合にはオブジェクトが再構築される。
int getDay()
曜日を取得する(0=日曜...)
long getTime()
void setTime(long)
エポックからの経過秒数を取得/セットする。セッタの場合にはオブジェクトが再構築される。
boolean after(Date)
before(Date)
引数の Date オブジェクトとの間で前後関係を判定する。
String toString()
デフォルトロケールで日時を文字表現する。
String toGMTString()
GMT表記によって日時を文字表現する。

ちなみに、java.text.DateFormat クラスを使うと、format メソッドによってロケールに応じた文字表現が可能になる。

java.util.Random

Random クラスは 疑似乱数を取得する。ゲームや暗号に必須なクラスである。

Random()
コンストラクタ。現在の時間を使って疑似乱数ジェネレータを作る。
Random( long seed )
コンストラクタ。seed を使って疑似乱数ジェネレータを作る。
int nextInt()
double nextDouble() など
それぞれの型に合わせた乱数を返す。

さて、次は java.util パッケージがあらかじめ用意していてくれているデータ構造である。これらは大変有用であるが、似たようなメソッドを備えたクラスが多く、どのクラスを選ぶのかを決めるには、そのクラスが Java のブラックボックス化とは別なレベルで、どういうデータ構造であるかをちゃんと理解する必要がある。

また、これらのクラスに格納されるのは Object クラスのオブジェクトである。逆にいえば、プリミティブ型の int などのデータは格納できないので、java.lang.Integer クラスのインスタンスにして格納する。また格納するときには代入規則によってキャストは不要だが、取り出すときには実データ型に代入して使うために、キャストが必要である。

また、これらのユーティリティの主要メソッドは、ほとんど synchronized 宣言されている。だからスレッドセーフであり、複数スレッドから同時にアクセスしたとしても、アトミックに動作することが保証されている。

java.util.Vector

Vectorクラスは可変長配列を実現している。realloc(3) で可変長配列を確保するのとほぼロジックは同じである(知らない人は「ポインタ虎の巻」を参照のこと!)。配列サイズに気を配る必要がないので、大変楽であるために頻繁に使われる。しかし、可変長配列なので、途中挿入、途中削除は効率が悪いことを注意すべきである。

void add( Object )
void addElement( Object )
末尾にデータを追加。Java 1.1 では add がないために、addElement の方を使う。
void add( int ind, Object o )
ind 位置にデータを挿入(考えてみれば良いが、これは効率が悪い)。
void setElementAt( Object o, int ind)
ind 位置のデータを o で置き換える。
void clear()
void removeAllelements()
すべてのデータを削除
Object elementAt( int ind )
ind 位置(添字)のデータを返す
boolean removeAt( int ind )
ind 位置のデータを削除(これも不効率)。
int size()
配列サイズではなく、現在入っている要素数を返す。
int capacity()
要素数ではなく、現在の配列サイズを返す。
Enumeration elements()
イテレータを返す(後述)。

java.util.LinkedList

双方向リスト(判らない人は「ポインタ虎の巻」を参照のこと!)を実現している。Vector クラスとは機能上補いあう関係にある。n 番目に到達するのにコストがかかるが、途中挿入と途中削除が効率的である。ということは、目的とするデータがあるかどうか検索するのはほぼ Vector と同じ効率ででき、見つけたものを削除するのは効率が良い。双方向リストなので、位置指定は最初もしくは最後の近い方から順次にアクセスされていく。

void add( Object o )
末尾にデータを追加。
void addFirst( Object o )
先頭にデータを追加(効率的)。
Object get(int ind)
ind 番目のデータを取得(不効率。頭or末尾から順に辿って番号をカウントする)。
Object getFirst(), getLast()
最初/最後のデータを取得(効率的)。
void remove( int ind )
ind 番目のデータを削除(検索不効率/削除は効率的)
void removeFirst()
removeLast()
最初/最後のデータを削除(効率的)
void remove( Object o )
o と内容が一致するデータを削除(比較的に効率的)

add( Object o, Object next ) [next オブジェクトの次に o オブジェクトを挿入]があると良いのだが、残念ながら存在していない。

java.util.Stack

Stack は Vector を継承してそれに push, pop 操作を加えたものである。Java の継承モデルでは、基底クラスのメソッドを狭めることができるので、本来これら以外のメソッドは private 宣言すべきであろうが、そうはなっておらずに Vector クラスのメソッドが呼べてしまうのが気持悪い。

また、空スタックを pop した時には、java.util.EmptyStackException を投げる。

boolean empty()
スタックが空であるかどうか判定する
void push(Object o)
push 操作を実現する
Object pop()
pop 操作を実現する

逆に言えば、Stack に対してこれら以外の操作を行うことは、Stack のメリットを無視したことになる。これら以外の操作は使うべきではない。

以上の説明でスタックが何たるか判らない人は、次の説明を読むべきであろう。

java.util.Properies

Properties は Hashtable クラスを継承し、キーと値とでペアを作って、キーから値を検索するデータ構造である。この検索は他のデータ構造による検索よりも効率的である。Hashtable との違いは、値のデータが String 型に限られることと、load と save という一括でデータのセット/書き出しができるメソッドを備えていることである。Hashtable クラスは大変有用なデータ構造であり、クラスライブラリの舞台裏でかなりよく使われている。

以上の説明でハッシュテーブルが何か判らない人は、次の説明を見るべきだろう。

void setPorperty( String key, String value)
キーと値のペアでプロパティをセットする
String getProperty( String key )
キーから値を検索する
Enumeration properyNames()
すべてのキーに関するイテレータを取得する(後述)
load(InputStream in)
入力ストリームから一括でプロパティをセットする
store(OutputStream out, String comment
comment をヘッダコメントとして、セットされているすべてのプロパティを書き出す

つまり、書き出されたプロパティファイルは次のような形式になる。

# comment の文字列
key1=value1

とはいえ、読み込む場合にはうまく工夫されていて、プロパティファイルの内容は次のようなものでも少しも問題はない。

key2    =  value2
# multiple values case
key3  apple, mango, banana
key4: value4

# で始まる行はコメントであると見なされるし、キーと値の区切り記号に =: も使える。

java.util.StringTokenizer と Enumeration インターフェイス

さて、今までもいくつか「イテレータ」という言葉を使ってきた。「イテレータ」とは繰り返しの構造を抽象化したものであり、デザインパターンの1つである。配列なりリストなり、複数のデータ構造を順番をもったセットにする方法はいくつかある。それらの具体的な実装構造に依存せず、最初から最後までのデータをシーケンシャルにアクセスする手段が「イテレータ」である。

この「イテレータ」は Java では interface として実現されている。一つは 1.0 からある Enumeration であり、もう一つは JDK 1.2 で新設された Iterator である。違いは削除のインターフェイスが追加されただけなので、ここでは Enumeration だけを解説する。

boolean hasMoreElements()
まだ未アクセスのデータがあるか。
Object nextElement()
新たに1つデータを取得する。

この Enumeration を使ったアクセスは次のようになる。

Properties myProp;
................
Enumeration e = myProp.propertyNames();
while( e.hasMoreElements() ) {
        String key = (String)e.nextElement();
        System.out.println( "key = " + key 
                         + " value = " + myProp.getProperty( key ) );
}

これで現在プロパティにあるすべてのデータを列挙できる。

この Enumeration interface をうまく使ったユーティリティとして、StringTokenizer クラスを挙げよう。これは文字列を分解するのに使う。さまざまなスクリプト言語で実装されている split() のような機能である。

StingTokenizer(String str)
コンストラクタ。文字列 str を分解対象にする。区切り文字はすべての空白文字である
StingTokenizer(String str, String delim)
コンストラクタ。文字列 str を、delim の中にあるすべての文字を区切り文字と考えて、分解対象にする。区切り文字自身はトークンとして扱われない。
int countTokens()
いくつのトークンに分解されたかを返す
boolean hasMoreElements()
まだ未アクセスのトークンがあるか。
String nextTokens()
Object nextElements()
双方とも、新しいトークンを返す。nextElements() の場合には String 型へのキャストが必要であることは言うまでもない。

このように使う。

String target = "apple , orange, lemon,   banana";
                /* , と空白が区切り文字になっている */
StringTokenizer st = new StringTokenizer( target, " ," );
while( st.hasMoreElements() ) {
        System.out.println( st.nextTokens() );
}



copyright by K.Sugiura, 1996-2006