先ほどのコードでは、プログラムの対象としているのがint型であり、メソッドやフィールドにint型がよく出てきていることに注目してください。
このプログラムをlong型のデータも扱うためのプログラムへ変更するには、どうすれば良いか考えてみましょう。キューのような有名なデータ構造を利用するプログラムはよくあるので、int型だけでなく、ほかのデータ型についても簡単に利用できるようにしたくなるものですね。
Queueクラスを変更して、long型データも対象とするクラスにするために、すぐ思い付く方法は、「int」と書いてある個所を、すべてlongへ置き換えた「LongQueue」クラスを別途用意する方法でしょう。そういわれると、「えー」と思う読者も多いことでしょう。これでは、新しいデータ型を扱うQueueクラスが必要になるたびに、新しいクラスを作成する必要が出てきて大変ですからね。
やはり、「処理の変更が必要ないなら、ソースコードを書き換えないで対応できないか」と思うはずです。そこで次に思い付くのが、データ型としてObject型を使うという方法です。Javaでは、Object型はすべてのクラスのスーパークラスですし、「基本データ型(プリミティブ型)」についても「ラッパークラス」により対応ができます。
ということで、Object型を使ったプログラムへ変更してみましょう。変更点は次のとおりです。コード上で変更があった行には「//」を付けてあります。
package sample19;
public class Sample02 {
static class Queue {
final int SIZE = 5;
private Object[] values = new Object[SIZE+1]; //
private int head = 0;
private int tail = 0;
boolean enqueue(Object data) { //
if (data == null) return false;
if (((tail + 1) % values.length) == head) {
return false;
}
values[tail++] = data;
tail = tail % values.length;
return true;
}
Object dequeue() { //
Object data = null; //
if (tail != head) {
data = values[head++];
head = head % values.length;
}
return data;
}
boolean isEmpty() {
return (tail == head);
}
}
public static void main(String[] args) {
Queue q = new Queue();
q.enqueue(1L); //
q.enqueue(2L); //
q.enqueue(3L); //
q.enqueue(4L); //
q.enqueue(5L); //
q.enqueue(6L); //
System.out.println(q.dequeue());
q.enqueue(7L); //
while (!q.isEmpty()) {
long data = (Long)q.dequeue(); //
System.out.print(data+",");
}
System.out.println("");
}
}
ここで、データを取り出すときの処理に注目してください。前回の「型変換」で学んだ「キャスト」を使っています。また、Long型からlong型へは「アンボクシング」によって暗黙のうちに型変換されています。
型について安全でない「ダウンキャスト」を使うことになるので、Object型を使ったキューのプログラムでは、キューへ代入した型について気を付けながらプログラミングをする必要が出てきます。実行結果は変わらないので省略します(以降、同様です)。
さて、このプログラムは意図したとおりに動作しますが、ダウンキャストしている点が気になります。いろいろな型のデータをキューへ入れたい場合には、Object型を扱うキューは便利なのですが、同じ型のデータだけをキューへ入れたい場合に、もっと良い方法はないのでしょうか。
こんなときのために、ジェネリックスというものが用意されているので、これを使ってみましょう。
ジェネリックスの機能を利用する場合は、「ジェネリック型(generic type)」としてクラスを宣言します。Eclipseでは、「generic type」のことを「総称型」と訳しているようで、Javaのコードを編集しているときに出る警告やコンパイル時のエラーでは、総称型という用語が出てきます。ここでは、ジェネリック型という用語を使います。
ジェネリック型のAクラスを定義するには、次のコードのようにします。
class A<T> {
T field1;
void method1(T t) {
}
}
「T」は、「型変数(type variable)」といいます。「class A
変数名は「T」でなくても構いません。「E」や「V」や「ElementType」など、ほかの文字列も使えます。1文字で指定することが多いようです。Aクラスの定義内で使われる型変数「T」は、Aクラスを利用するときに指定される型に置き換わります。
Aクラスを使う場合、型パラメータ「T」に対応する「型引数(type argument)」を指定します。次の例では、具体的なクラス名として「String」を指定しています。これにより、先ほどのAクラスで「T」と指定されていた型がjava.lang.Stringとなります。
A<String> a;
「A
ジェネリック型に対して型引数を指定せずに使うこともできます。この場合の型を、「原型(raw type)」といい、通常はコンパイラが警告を出します。例えばEclipseでは「Aはraw型です。総称型A
A a;
具体的にQueueをジェネリックスの機能を使って実装するとどうなるか、次のコードを見てみましょう。
package sample19;
public class Sample03 {
static class Queue<T> { //
final int SIZE = 5;
private Object[] values = new Object[SIZE+1];
private int head = 0;
private int tail = 0;
boolean enqueue(T data) { //
if (data == null) return false;
if (((tail + 1) % values.length) == head) {
return false;
}
values[tail++] = data;
tail = tail % values.length;
return true;
}
T dequeue() { //
T data = null; //
if (tail != head) {
data = (T)values[head++]; //
head = head % values.length;
}
return data;
}
boolean isEmpty() {
return (tail == head);
}
}
public static void main(String[] args) {
Queue<Long> q = new Queue<Long>(); //
q.enqueue(1L);
q.enqueue(2L);
q.enqueue(3L);
q.enqueue(4L);
q.enqueue(5L);
q.enqueue(6L);
System.out.println(q.dequeue());
q.enqueue(7L);
while (!q.isEmpty()) {
long data = q.dequeue(); //
System.out.print(data+",");
}
System.out.println("");
}
}
Queueクラスの宣言時に、型変数として「T」を指定している点に注目してください。また、enqueueメソッド、dequeueメソッドでも型変数を使った宣言になっている点にも注目してください。変更があった行には「//」を付けてあります。
使うときは、Sample03クラスのmainメソッドのようになります。Long型のデータを扱うキューとして「Queue<Long> q」のように記述します。インスタンス生成時にも「new Queue<Long>();」と型を指定します。
ジェネリックスを使うことで、Sample02クラスでは「long data = (Long)q.dequeue();」としてダウンキャストが必要だった処理が「long data = q.dequeue();」のように簡潔に記述できるようになっています。
ただし、利用する場合のダウンキャストは必要なくなりましたが、Queueクラスの中でダウンキャストが必要となっている点を見落とさないようにしてください。
次に、Integer型のデータを扱うキューとしてSample03.Queueクラスを使ってみましょう。Sample03.Queueクラス側へ変更を加える必要はなく、Sample04クラスのように利用できます。
package sample19;
public class Sample04 {
public static void main(String[] args) {
Sample03.Queue<Integer> q = new Sample03.Queue<Integer>();
q.enqueue(1);
q.enqueue(2);
q.enqueue(3);
q.enqueue(4);
q.enqueue(5);
q.enqueue(6);
System.out.println(q.dequeue());
q.enqueue(7);
while (!q.isEmpty()) {
int data = q.dequeue();
System.out.print(data+",");
}
System.out.println("");
}
}
どうでしょうか。ジェネリック型を使うと、Queueへ入れるデータ型を指定できるので、Object型を使うよりも型について安全になることが分かったと思います。
こういったジェネリックスの便利さが分かってくると、どんどんと使いたくなるはずです。さらにジェネリックスを使いこなすために、次ページでは、Java APIのジェネリック型やワイルドカードについて説明します。
Copyright © ITmedia, Inc. All Rights Reserved.