Java API
ここまでの講義は、Java に特化したものではなく「オブジェクト指向言語全般」を学習するためのものであった。それらを理解したからといって Java の複雑なプログラムを書けるようになるわけではない。
例えば、ここまでの講義で得られる知識だけでは、次のようなプログラムを書くことはできない。
- ファイルを扱うようなプログラム
- Internet Explorer のようなインターネットにアクセスできるプログラム
- Eclipse のような Java を開発するためのプログラム
上記のようなプログラムは、Java が提供する様々な機能を最大限に利用し、Windowsなどオペレーティングシステムと連携しないと機能を実現することができない。今回以降の講義では内容を Java に絞り、Java で複雑なプログラムを書くための導入を行う。
Java API とは
API とは、Application Programming Interface (アプリケーションを作成する際に、システムが提供する機能を使用するためのインターフェース) の略で、Java にも Java の機能を有効に使うために多数のAPIが用意されている。
Javaで提供されるAPIは全てクラスやインターフェースの形をとっていて、それらのクラスを new してインスタンス化したり、クラスメソッドを呼び出すことによって必要な機能を使用することができる。
例えば、今まで使用してきた次のようなものは、すべて Java が提供する API である。
- System.out.println
- System クラスが持つ out というクラスフィールドを取得
- out というクラスフィールドに格納されたインスタンスの println メソッドを呼び出し
- Math.sqrt
- Math クラスが持つ sqrt というクラスメソッドを取得
- String
- String クラス。length() charAt(int) などのインスタンスメソッドを持つ
API を使う利点
API を使う最大の利点は、すでに用意されてる API を利用することによって、プログラムの本質的な問題に集中できるという点である。
例えば、「2つの整数を入力し、その和を表示」というプログラムを考えた場合、細かいプログラムの流れは次のようになる。
- コンソール入力を取得できるようにする
- 変数 input1 を 0 に設定する
- コンソール入力を一文字取得し、改行文字が来るまで次の処理を行う (*1)
- input1 の値を 10 倍する
- 一文字を数値に変換し、input1に加算する
- 変数 input2 を 0 に設定する
- コンソール入力を一文字取得し、改行文字が来るまで次の処理を行う (*1)
- input2 の値を 10 倍する
- 一文字を数値に変換し、input2に加算する
- input1 と input2 の和を計算し、result に代入する
- 文字の配列 str を作成する
- result の各桁に対して、次の処理を行う
- 最上位の桁を取得して、文字に変換する
- 変換した文字を str に追加する
- コンソールに文字列を書き込めるようにする
- 変換した文字列をコンソールに順番に書き出す
- プログラムを終了させる
- (*1)
「'1', '2', '3', 改行文字」を入力したとき、123 と言う整数を入力したとするためには次のような計算をすればよい。
input1=0; input1=input1*10+1; //最初の入力'1'、input1の値は1になる。 input1=input1*10+2; //入力'2'、input1の値は12になる。 input1=input1*10+3; //入力'3'、input1の値は123になる。
このような計算を改行文字を読むまで続ける
プログラムの本質は、文字列を数値に変換したり、数値を文字列に変換する部分ではなく、「2つの整数を入力し、その和を表示」という部分である。実際にプログラムを書く場面になれば、上記のようなことを考えずに次のようなプログラムを書くはずである。
// コンソール入力を取得できるようにする BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // 変数 input1 を 0 に設定する // コンソール入力を一文字取得し、改行文字が来るまで次の処理を行う // input1 の値を 10 倍する // 一文字を数値に変換し、input1に加算する int input1 = Integer.parseInt(reader.readLine()); // 変数 input2 を 0 に設定する // コンソール入力を一文字取得し、改行文字が来るまで次の処理を行う // input2 の値を 10 倍する // 一文字を数値に変換し、input2に加算する int input2 = Integer.parseInt(reader.readLine()); // input1 と input2 の和を計算し、result に代入する int result = input1 + input2; // 文字の配列 str を作成する // result の各桁に対して、次の処理を行う // 最上位の桁を取得して、文字に変換する // 変換した文字を str に追加する // コンソールに文字列を書き込めるようにする // 変換した文字列をコンソールに順番に書き出す System.out.println(result); // プログラムを終了させる
上記の例では、コンソールへの入出力や数値と文字列の相互変換を API を用いて行った。これまでの講義ですでに API を多数利用していることがわかる。
他にも利点がいくつかある。
- APIで提供される機能は、自分で作るプログラムよりも高速に動作することが多い。
- APIで提供される機能は、十分にテストされている。自分で作るよりもバグが少ない。
- 他人がプログラムを読んだ際に、APIを用いれば説明の手間が省ける。
Java API 仕様
Java の API は無数に用意されている。全てを覚えることは大変なので、通常は API 仕様 というものを参照しながらプログラムを作成する。
- Java2 Platform, Standard Edition, 1.4.0 (http://java.sun.com/j2se/1.4/ja/docs/ja/api/)
- Java 2 Platform Standard Edition 5.0 (http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/index.html)
本講義で扱っているJavaのバージョンは「J2SE 1.4.0」であるが、2005年10月の時点でそれよりも新しいバージョンの「Java SE 5.0」が存在する。新しいバージョンでは多数のAPIが追加されたほか、新しい言語の要素がいくつも追加されている。新しい言語の要素は本講義では触れない。
API 仕様のページを開くと、次のような構成になっている。
- (1) - パッケージ一覧
- (2) - パッケージ内のクラス, インターフェース一覧
- (3) - 選択したクラスやパッケージの解説
ここまでの講義は、Java に特化したものではなく「オブジェクト指向言語全般」を学習するためのものであった。それらを理解したからといって Java の複雑なプログラムを書けるようになるわけではなく、プログラミングの能力は「基本的なプログラミング能力」や「対象の言語をどの程度使いこなせているか」ということに加えて「その言語のAPIをどの程度使用できるか」というものに左右される。
基本的なプログラミング能力がなければ API を使うことすらできないが、基本的なプログラミング能力だけあってもシステムの機能を十分に生かすには API を使わざるを得ない場面がある。例えば「コンソールに文字列を表示する」という簡単な例一つをとっても、コンソールに文字列を表示させるにはオペレーティングシステムのサポートが不可欠である。このような場面で API が使われる。
空いた時間で、少しずつこのドキュメントに目を通すこと。その際は、全てを覚えようとするのではなく、次のようなことを意識してみるとよい。
- どのようなクラスがあるか眺める
- 使えそうなクラスがあれば、そのクラスのメソッドを眺める
- このクラスを使うと「どのようなことができるのか」ということを頭に入れておく
例えば、java.util.GregorianCalendarというものがあるが、これは日付や時刻を表すクラスである。それだけ覚えていれば「時刻を扱うプログラム」を書く際に、API仕様で GregorianCalendar のクラスについて調べながらプログラムを書けばよい。
パッケージ
Java の API に含まれる数々のクラスは、その機能や目的別にパッケージにまとめて格納されている。
一例を挙げると、以下のようなものがある。
パッケージ | 内容 |
---|---|
java.lang | Javaの基本的なクラス |
java.util | 一般的なデータ構造や、カレンダーなどのユーティリティクラス |
java.io | データストリームやファイルの入出力に関するクラス |
java.net | ネットワークに関するクラス |
例えば、「ファイルを読み出して処理を行う」ようなプログラムを書く場合、java.io パッケージ下にあるクラスについて調べればいいし、「ウェブページの情報を取得して処理を行う」ようなプログラムを書く場合、java.net パッケージ下にあるクラスについて調べればいい。パッケージの意味を覚えると、使いたい機能を持つクラスを探すのが容易になる。
Java API の習得
おそらく Java の API だけに限ったことではないと思うが、Java API を習得する方法を紹介する。
- プログラムを書く際に使いたい機能が Java API で提供されていないか、常に調べる習慣を身につける
- とにかく Java API を使ったサンプルプログラムを書く
- 各パッケージがどのようなものを提供するか覚える
- 各パッケージ内にどのようなクラスが含まれるか覚える
- 各クラスがどのような機能を提供するか覚える
いくらかは覚える必要があるが、完全に覚える必要はない。あるクラスが提供する機能を完全に覚えている時間があるなら、その時間を使って他の様々なクラスの機能を調べたほうがよい。使っていくうちにある程度は覚えてしまうので、常に使い続けてさえいれば、すぐに主要なAPIは頭の中に入れることができるようになる。
文字列を扱うクラス
これまでの講義で一番多く扱ったクラスは、おそらく文字列を表す String クラスである。String クラスのインスタンスは "" で囲むことによって作成することができ、また、+ 演算子によって String インスタンスが表す文字列と他の値を連結させた新しい文字列を作ることができた。
String hello = "Hello"; int number = 100; String hello100 = hello + 100; // System.out.println("Hello" + 100); System.out.println(hello100);
String が持つメソッドとして、文字列内の文字を取得する charAt(int) や、文字列の長さを比較する length() などをこれまでに紹介してきた。
文字列の比較
下記は、「コンソールに文字列を入力させ、その文字列が "hello" ならば "world!" を、それ以外の入力ならば "???" を表示するプログラム」のつもりで書いたものである。
package j2.lesson07.example; import java.io.*; public class StringEquals1 { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // "hello" と入力させ、str1 に代入 System.out.print("input>"); String str1 = reader.readLine(); // str2 にも "hello"を代入 String str2 = "hello"; // "hello" == "hello"? if (str1 == str2) { System.out.println("world!"); } else { System.out.println("???"); } } }
このプログラムを実行して hello と入力しても、結果は次のように ??? となってしまう。
input>hello ???
これは、文字列の比較を == 演算子 を用いて行っているからである。この演算子は、次のような比較を行う。
調べたいのは「文字列の中身が同じかどうか (同値性)」であったのに、実際に行った比較処理は「同じインスタンスを指し示すかどうか (同一性)」である。
次のようなプログラムで、2つの文字列の同値性を調べることができる。
package j2.lesson07.example; import java.io.*; public class StringEquals2 { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("input>"); String str1 = reader.readLine(); String str2 = "hello"; // 同じ文字列であるためには、まず長さが同じでなければならない if (str1.length() == str2.length()) { // 同じ文字列であるためには、全ての文字が同じでなければならない boolean same = true; for (int i = 0; i < str1.length(); i++) { if (str1.charAt(i) != str2.charAt(i)) { // 一つでも違えば違う same = false; } } if (same) { // 一つも違う文字がなければ「同じ文字列」 System.out.println("world!"); } else { System.out.println("???"); } } else { System.out.println("???"); } } }
上記のプログラムは、次のような比較処理を行っている。
- 2つの文字列の長さが同じかどうか比較
- 2つの文字列が持つそれぞれの文字が同じかどうか比較
このプログラムを実行して "hello" と入力すると、次のような期待した結果になる。
input>hello world!
文字列の同値性を判定するたびに上記のようなプログラムを書いていたら、それだけで労力を使ってしまう。Java の java.lang.String クラスには、次のような同値性を判定するメソッド equals が用意されている。
package j2.lesson07.example; import java.io.*; public class StringEquals3 { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("input>"); String str1 = reader.readLine(); String str2 = "hello"; // 先ほどの処理を equals で書ける if (str1.equals(str2)) { System.out.println("world!"); } else { System.out.println("???"); } } }
str1.equals(str2) という書き方をした equals メソッドは、2つの文字列が等しければ true を返し、等しくなければ false を返すようなメソッドである。このようなメソッドを使うことによって、直感的に分かりやすいプログラムを書くことができるし、間違いが少なくなる。
Stringで使えるメソッド
String クラスで提供されるメソッドについては下記のURLを参照すること。
長さを調節できる配列
「空の文字列が入力されるまでコンソールから入力を行い、空の文字列が入力されたらそれまでに入力された文字列を出力するプログラム」を考える。
これまで、複数のデータを格納できるデータ構造として、配列について紹介した。配列を使ってこのプログラムを実装した場合、問題となるのが「空の文字列が入力されるまで」という部分である。配列は new 演算子によって作成する際に、その長さを決めておかなければならない。
「空の文字列が入力されるまで」という内容では、配列を作成する際に文字列が何回入力されるか予想が付かないため、次のように「配列がいっぱいになったらさらに大きな配列を用意する」というプログラムを書く必要がある。
package j2.lesson07.example; import java.io.*; public class ExtensibleArray { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // とりあえず 5 個の文字列を格納できる配列を作る String[] lines = new String[5]; // 入力された個数 int count = 0; while (true) { // 文字列を読み取る String input = reader.readLine(); // 空の文字列 ("") なら終了 if (input.equals("")) { break; } // 配列に代入する…が、その前に // 配列がすでにいっぱいならば、配列の大きさを拡張する if (count == lines.length) { // 現在より 5 大きい配列 String[] exlines = new String[lines.length + 5]; // 今までの内容を新しい配列にコピー for (int i = 0; i < lines.length; i++) { exlines[i] = lines[i]; } // これから先は新しい配列を使う lines = exlines; } // 配列に代入する lines[count] = input; count++; } // 入力された内容を出力する for (int i = 0; i < count; i++) { System.out.println(lines[i]); } } }
上記のように長いプログラムになってしまった。これを使うと、下記のように5行以上の入力を行っても大丈夫である。
hello (1) world (2) こんにちは (3) 世界 (4) ここで初期配列の限界 (5) ここで配列を拡張 (6) 次の行で終わり (7) hello (1) world (2) こんにちは (3) 世界 (4) ここで初期配列の限界 (5) ここで配列を拡張 (6) 次の行で終わり (7)
このようなプログラムを作成する際に、毎回上記のように配列の大きさを拡張させる処理を書いていては大変である。Java にはこのようなことを自動的に行ってくれるデータ構造として、java.util.ArrayList というクラスが用意されている。
ArrayList には、add(Object) というメソッドが用意されていて、「データの末尾に指定した値を追加する」といったものである。このメソッドを使うと、内部の配列の長さが足りない場合には上記のような「配列の長さを拡張する処理」を自動的に行ってくれるため、配列の長さを気にせずにプログラムを書くことができる。
他にも、配列で使っていた代入や参照といった処理もメソッドとして用意されている。
内容 | 配列 | ArrayList |
---|---|---|
値を代入 | array[i] = obj | list.set(i, obj) |
値を参照 | obj = array[i] | obj = list.get(i) |
この ArrayList を用いて先ほどのプログラムを書き直すと、次のようになる。
package j2.lesson07.example; import java.io.*; import java.util.ArrayList; public class ExtensibleArrayList { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // 可変長の配列である ArrayList を使う ArrayList lines = new ArrayList(); while (true) { // 文字列を読み取る String input = reader.readLine(); // 空の文字列 ("") なら終了 if (input.equals("")) { break; } // ArrayList に追加する lines.add(input); } // 入力された内容を出力する for (int i = 0; i < lines.size(); i++) { System.out.println(lines.get(i)); } } }
非常に短いプログラムになった。ただし、size() というメソッドは、「格納している要素の個数」を取得するメソッドである。配列の .length は「格納できる要素の個数」で少し異なるので注意する必要がある。
型キャスト
子クラス型のデータ (インスタンス) を、親クラスの型を持つ変数に代入することができた。また、メソッドの引数でも同じことができた。
ArrayList に格納できるデータは全て Object 型である。全てのクラスは Object クラスを継承しているため、全てのインスタンスは ArrayList に格納することができる。そのため、add メソッドや set メソッドを使えば ArrayList に全ての種類のインスタンスを格納することができる。
しかし、add や set などのインスタンスを格納するメソッドで全てのインスタンスを Object 型として格納したため、get などの格納された値を取得するメソッドを呼び出した際にも Object 型で返ってきてしまう。
先ほどの ArrayList を使った例で、「入力された内容」と「入力された文字数」を表示する場合、次のようなプログラムを書きたくなる。
... while (true) { String input = reader.readLine(); if (input.equals("")) { break; } lines.add(input); } // 入力された内容と入力された文字数を表示する for (int i = 0; i < lines.size(); i++) { String str = lines.get(i); System.out.println(line + "(" + str.length() + "文字)"); }
上記のプログラムは、コンパイルエラーになる。「lines.add(input)」は add(Object) メソッドに対して String 型のデータを渡しているため問題ない。これは、「String is-a Object」なので、String型のデータをObject型として扱えるためである。しかし、「lines.get(i)」は Object 型のデータが返されるが、「Object is-a String (オブジェクトは文字列である)」とは限らないので取得した値を String 型の変数 str に代入することができないためである。
確かに、上記のプログラムだけを人間が見れば「lines.get(i)」は全ての i において String のインスタンスが返ってくることは予想できる。しかし、厳格に型を調べるコンパイラはそれを知るすべがないため、明示的に教えてやる必要がある。
このような代入を行いたい場合は、型キャスト演算子 というものを使って、「lines.get(i) が返す値は String 型であるはず」というヒントをコンパイラに与えてやり、データの持つ型を Object 型から明示的に String 型に変換する必要がある。
for (int i = 0; i < lines.size(); i++) { String str = (String) lines.get(i); System.out.println(line + "(" + str.length() + "文字)"); }
型キャスト演算子は、「(変換先の型) データ」という書き方をすることによって、指定したデータの型を変換することができる。実際に指定した型に変換できなかった場合は、プログラムの実行中にエラーが発生する。
このキャスト演算子は、int, double 型などの数値を扱う型にも使用することができる。
int 型や double 型などの数値を扱う型や、char などの文字を扱う型はプリミティブ型と呼ばれ、Objectを継承していない。そのため、toString() などの有用なメソッドは用意されていないが、加算や減算などの基本的な演算子を使用することができる。
それに対して、String 型や自分で定義したクラスのインスタンスはリファレンス型と呼ばれ、親クラスにObjectクラスを直接的 (直接の親) にしろ間接的 (親の親など) にしろ持つ。
int 型の場合、その全ての値は double 型で表現することができる。そのため、キャスト演算子を使用しなくてもそのまま代入できる。
double d = 1 + 4;
逆に、double 型の全ての値を int 型で表現することができないので、double 型から int 型への変換はキャスト演算子が必要となる。
int i = (int) Math.PI;
プリミティブ型は他にもいくつか存在するが、ここでは紹介しない。詳しくはJava 言語仕様を参照のこと。
ラッパークラス
java.util.ArrayList では、Object 型のインスタンスしか扱うことができないが、Java では全てのクラスは Object クラスを継承しているため、これらは問題ない。しかし、int や double 等のプリミティブ型は Object 型を継承していないため、ArrayList で使用することができない。
そこで、Java にはプリミティブ型を Object クラスを継承したクラスとして扱うために、ラッパークラスというものが存在する。
- java.lang.Integer -> int 型のラッパー
- java.lang.Double -> double 型のラッパー
全てのプリミティブ型に上記のようなクラスが存在している。プリミティブ型の値は、このクラスのインスタンスに変換することによって Object 型として扱うことができるようになる。
使い方は簡単で、new 演算子を用いて上記クラスのインスタンスを作成する際に、コンストラクタの第一引数としてプリミティブ型の値を指定するだけである。
Integer intWrap = new Integer(100); Double doubleWrap = new Double(3.14); ArrayList list = new ArrayList(); list.add(intWrap); list.add(doubleWrap);
上記の例では、int 型の 100 という値を Integer 型に、double 型の 3.14 という値を Double 型にそれぞれ変換している。Integer, Double クラスはやはり Object クラスを継承しているため、List に格納することができる。
ラッパークラスのインスタンスをプリミティブ型に戻すには、intValue(), doubleValue() などの「プリミティブ型 + Value()」というメソッドを呼び出せばよい。
Integer intWrap = new Integer(100); Double doubleWrap = new Double(3.14); int intUnwrap = intWrap.intValue(); double doubleUnwrap = doubleWrap.doubleValue();
ラッパークラスを用いると、ArrayList で整数などの値を扱えるようになる。
package j2.lesson07.example; import java.util.ArrayList; public class ListFor { public static void main(String[] args) { ArrayList list = new ArrayList(); // int -> Integer に変換 list.add(new Integer(5)); list.add(new Integer(10)); list.add(new Integer(20)); // for 文で各要素にアクセス for (int i = 0; i < list.size(); i++) { // get して Object -> Integer にキャスト Integer element = (Integer) list.get(i); // Integer -> int に変換 int value = element.intValue(); // int -> Integer に変換して set list.set(i, new Integer(value * 3)); } // 全て表示 System.out.println(list); } }
上記のプログラムを実行すると、次のような結果が表示される。
[15, 30, 60]
System.out.println メソッドを、引数に Object 型のデータを渡して呼び出すと、引数に対して toString() メソッドを呼び出した結果を表示する。表示された結果のように、ArrayList のオブジェクトに対して toString() を呼び出すと、上記のように中身まで表示してくれるので非常に便利である。
ArrayList と似たクラス
java.util.ArrayList と同じような機能を持つクラスとして、下記の 2 つがある。
- java.util.LinkedList
- java.util.Vector
LinkedList は先頭や末尾への要素の追加削除が高速に行えるデータ構造で、そのような機能がほしい場合によく使われる (ArrayList は先頭への追加削除がやや低速であり、LinkedList は中央に位置するデータへの参照がかなり低速である)。Vector は古いプログラムで一部使われるのみである。
ArrayList, LinkedList, Vector はどれも java.util.List というインターフェースを実装している。そのため、どのクラスを使う際にも List 型のオブジェクトとして扱うことができる。
List alist = new ArrayList(); List llist = new LinkedList(); List vlist = new Vector();
List インターフェースは、ArrayList で実装されている get, set, add といったメソッドを持っている。そのため、ArrayList 型として宣言してきたインスタンスを List 型で宣言することによって、LinkedList や Vector を同じプログラムで扱うことができるようになる。
例えば、java.util.Collections というクラスに sort(List) というクラスメソッドがある。これは List 型のインスタンスが持つ各要素をソートするためのものであるが、この引数が List 型であることによって ArrayList, LinkedList, Vector などのどの実装でもこの sort メソッドを使うことができる。
上記のどの実装を使ってもよいが、特別な理由がない限りは「ArrayList」を使うべきである。LinkedList や Vector を使える場面もいくつかあるが、ほとんどの場合ではスピードの面で不利となる。LinkedList など他の実装を使う場合、どのようなものかしっかり理解していないと非常に遅いプログラムになってしまうので注意すること。
List で使えるメソッド
詳しくは、List のAPI仕様や ArrayList, LinkedList, Vector の API仕様を調べること。
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/List.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/ArrayList.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/LinkedList.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/Vector.html
辞書のようなデータ構造
国語辞典などの辞書は、辞書で調べたい単語を引くとその単語の意味を取得できる。このようなデータ構造を表す場合について考える。
簡単な辞書として、「英語の one, two, three, four, five といった 5 つの単語のうちどれかを入力すると、その意味として 1, 2, 3, 4, 5 といった数値を取得できるようなプログラム」を作成する。プログラムではコンソールから英語で数値を入力し、その単語が表す数字を表示する。
package j2.lesson07.example; import java.io.*; public class NumberDictionary1 { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("input> "); String input = reader.readLine(); // 入力された文字列が辞書に入っているか調べる if (get(input) != -1) { // 入っているなら意味を一緒に表示する System.out.println(input + "は" + get(input)); } else { System.out.println(input + "は辞書にありません"); } } // 辞書から単語を引く (if-else で辞書を表現する) private static int get(String word) { if (word.equals("one")) { return 1; } else if (word.equals("two")) { return 2; } else if (word.equals("three")) { return 3; } else if (word.equals("four")) { return 4; } else if (word.equals("five")) { return 5; } else { // 不明な値 return -1; } } }
上記は if-else の連鎖で辞書を表現したが、このようなプログラムの書き方で辞書を変えるにはプログラムを書き換えてコンパイルしなおす必要がある。例えば、プログラムの実行中に辞書に単語を追加するなどの処理が非常に難しい。
そこで、先ほどの ArrayList を用いて上記のプログラムを書き直してみる。
package j2.lesson07.example; import java.io.*; import java.util.*; public class NumberDictionary2 { // 単語リスト private static List keys = new ArrayList(); // 意味リスト private static List values = new ArrayList(); public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // 単語リストと意味リストを作成 put("one", 1); put("two", 2); put("three", 3); put("four", 4); put("five", 5); System.out.print("input> "); String input = reader.readLine(); // 入力された文字列が辞書に入っているか調べる if (get(input) != -1) { // 入っているなら意味を一緒に表示する System.out.println(input + "は" + get(input)); } else { System.out.println(input + "は辞書にありません"); } } // 辞書に単語を追加する public static void put(String word, int number) { // 単語とそれを表す数値は、別のリストの同じインデックスに格納する keys.add(word); values.add(new Integer(number)); } // 辞書から単語を取得する public static int get(String en) { // 入力された英語と同じ文字列を keys から探す int index = keys.indexOf(en); // 見つかれば 0 以上のインデックスが返る if (index != -1) { // key と value は同じインデックスに格納した Integer n = (Integer) values.get(index); return n.intValue(); } else { return -1; } } }
上記のプログラムは、List を用いて先ほどのプログラムに機能を追加したものである。put(String word, int number) というメソッドは、単語リストと意味リストにそれぞれ「英単語」とそれが表す「数値」を追加するもので、ここで追加した単語を get(String word) メソッドで取得することができる。
get メソッドでは、List の indexOf(Object) というメソッドを使用した。このメソッドは引数に指定した要素が、リスト内のどこに格納されているか調べるメソッドで、見つかった場合はその格納されている位置 (インデックス) を返す。見つからなかった場合は -1 を返すようなメソッドである。
上記のように書くことにより、プログラムを書き直して再度コンパイルすることなく、put メソッドを呼び出すだけで辞書にデータを追加できる。
この「辞書」のようなデータ構造は、プログラムを書く上で使われることが多い。Java にはこのようなデータ構造を表すクラスとして、java.util.HashMap というものが最初から用意されている。
java.util.HashMap はキーと値のペアを持ち、キーを使ってそれとペアとなっている値を調べることができるようなデータ構造である。主な機能として、次のようなメソッドを持つ。
- キーと値のペアを登録する put(Object key, Object value)
- キーを指定してペアになっている値を取得する get(Object key)
- キーが登録されているか調べる containsKey(Object key)
Map を使って先ほどのプログラムを書き直すと、以下のようになる。
package j2.lesson07.example; import java.io.*; import java.util.HashMap; public class MapSample { public static void main(String[] args) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); // HashMap のインスタンスを作る HashMap dictionary = new HashMap(); // 英語 から 数値 を取得できる辞書 dictionary.put("one", new Integer(1)); dictionary.put("two", new Integer(2)); dictionary.put("three", new Integer(3)); dictionary.put("four", new Integer(4)); dictionary.put("five", new Integer(5)); System.out.print("input> "); String input = reader.readLine(); // 入力された文字列が辞書に入っているか調べる if (dictionary.containsKey(input)) { // 入っているならキーに対応する値を表示する System.out.println(input + "は" + dictionary.get(input)); } else { System.out.println(input + "は辞書にありません"); } } }
上記のプログラムを実行して、「three」と入力すると、「threeは3」と表示される。これは、"three"というキーと"3"という値がペアになって登録されているので、正しくその値を取得できる。また、「six」と入力すると、「sixは辞書にありません」と表示される。"six"というキーが登録されていないからである。
"nine" というキーと 9 という値のペアをさらに追加するには、下記の文を追加すればよい。
dictionary.put("nine", new Integer(9));
HashMap と似たクラス
java.util.HashMap と同じような機能をもつクラスとして、下記のものがある。
- java.util.TreeMap
- java.util.LinkedHashMap
- java.util.Hashtable
- (java.util.IdentityHashMap)
- (java.util.WeakHashMap)
それぞれ HashMap と同様に put, get, containsKey などのメソッドが用意されている。TreeMap はキーを昇順に並べながら管理するという特徴を持つが、キーを比較するためのメソッドを独自に用意する必要がある。Hashtable は古いプログラムで一部使われるのみである。括弧で表記した実装クラスは特殊な意味を持つため、使う場面が特に限られる。
上記はどれも java.util.Map というインターフェースを実装している。そのため、どのクラスを使う際にも Map 型のオブジェクトとして扱うことができる。
Map tmap = new TreeMap(); Map lmap = new LinkedHashMap(); Map htbl = new Hashtable();
ただし、特別な理由がない限りは「HashMap」を使うべきである。
Mapで使えるメソッド
詳しくは、Map のAPI仕様や HashMap, TreeMap, LinkedHashMap, HashTable の API仕様を調べること。
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/Map.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/HashMap.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/TreeMap.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/LinkedHashMap.html
- http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/util/Hashtable.html
まとめ
API とは、Application Programming Interface (アプリケーションを作成する際に、システムが提供する機能を使用するためのインターフェース) の略で、Java にも Java の機能を有効に使うために多数のAPIが用意されている。
それらを使いこなすことによって、問題の本質に重点的に力を注ぐことができ、高品質なプログラムを作成することができる。
APIは膨大な量が用意されているので、常に API 仕様 というものを参照しながらプログラムを作成し、少しずつ頭に入れておくとよい。
- Java2 Platform, Standard Edition, 1.4.0 (http://java.sun.com/j2se/1.4/ja/docs/ja/api/)
- Java 2 Platform Standard Edition 5.0 (http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/index.html)
常に次のことを心がけていれば、API を使えるようになってくる。
- プログラムを書く際に使いたい機能が Java API で提供されていないか、常に調べる習慣を身につける
- とにかく Java API を使ったサンプルプログラムを書く
- 各パッケージがどのようなものを提供するか覚える
- 各パッケージ内にどのようなクラスが含まれるか覚える
- 各クラスがどのような機能を提供するか覚える