japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2009年7月4日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2005年12月27日 09:30

J2SE 5.0を使ったカスタムジェネリックコレクションの作成

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!
  • この記事をokyuuへインポート

はじめに

私の前回の記事では、J2SE 5.0の新しいコレクションの機能を用いて、コレクションで使用する特定の型を指定する方法を説明しました。また、新たに導入されたfor each構文を利用すれば、「反復子(イテレータ)」を使わずにコレクションを参照できることも説明しました。しかし、前回の記事で紹介したのは、コレクションの新機能の半分でしかありません。この記事では、J2SEの最新機能に適合したコレクションを作る方法を紹介します。

ジェネリックをサポートするクラスの作成

 まず、「ジェネリック型」をサポートするクラスの作り方を学ぶ必要があります。「ジェネリック型をサポートする」とは、クラスのインスタンス作成を行うたびに、そのインスタンスに関連付けるJavaの型を1つ以上指定できることを意味します。これを説明するために、リスト1の簡単なサンプルクラスについて考えてみましょう。

リスト1 ジェネリッククラス
package com.heatonresearch.examples.collections;
public class Example<ONE, TWO, THREE> {
  private ONE one;
  private TWO two;
  private THREE three;

  public ONE getOne() {
    return one;
  }

  public void setOne(ONE one) {
    this.one = one;
  }

  public THREE getThree() {
    return three;
  }

  public void setThree(THREE three) {
    this.three = three;
  }

  public TWO getTwo() {
    return two;
  }

  public void setTwo(TWO two) {
    this.two = two;
  }

  public static void main(String args[]) {
    Example<Double, Integer, String> example
      = new Example<Double, Integer, String>();
    example.setOne(1.5);
    example.setTwo(2);
    example.setThree("Three");
  }

}

 リスト1のクラスがどのように宣言されているかに注目してください。最初の部分にある山かっこ内に3つのジェネリックが記されています。これらのジェネリックは、実際の型が入るプレースホルダです。この種のクラスをインスタンス化するときは、"ONE"、"TWO"、および"THREE"の代わりとなる具体的なクラス名を指定できます。指定を行わない場合、このクラスはデフォルトのオブジェクト型を使用します。

 次に示すのは、Example型クラスのインスタンスを作成する方法です。

Example<Double, Integer, String> example
  = new Example<Double, Integer, String>();

 上記のコードは、指定したDoubleIntegerStringの各型をリスト1の"ONE"、"TWO"、"THREE"の各プレースホルダにあてはめます。各変数に値を設定する次の3行のコードを見ると、それぞれの変数が指定どおり別々の型を持つことがわかります。

example.setOne(1.5);
example.setTwo(2);
example.setThree("Three");

 これで、ジェネリックを用いたカスタムクラスの作り方がわかりました。この点を押さえておけば、ジェネリックを利用するカスタムのコレクションクラスを作るのはもっと簡単です。

キュークラスの作成

 キューというデータ構造が非常に役に立つ場合があります。キューの機能を理解するために、遊園地で乗り物の順番を待つ人々の列を想像してみましょう。新たに列に加わる人は、列の最後尾に並びます。順番を待っていれば、いつかは列の先頭にたどり着きます。並んでいる人々の順序が入れ替わることはありません。

 これと同じ原理が、キュークラスにもあてはまります。キュークラスには、pushpopという2つのメソッドがあります。キューにオブジェクトを入れるにはpushメソッドを、キューからオブジェクトを取り出すにはpopメソッドを使います。たとえば、pushメソッドを使って3つのオブジェクトをキューに追加した場合、popメソッドを3回呼び出せば、この3つのオブジェクトは追加したときと同じ順序で取り出されます。遊園地の順番待ちの列と同じです。ある順序で列に並んだ3人は、同じ順序で乗り物にたどり着くわけです。

 次のコードは、Javaによる、ジェネリックを利用したキューの実装方法を示しています。

package com.heatonresearch.examples.collections;

import java.util.*;

public class Queue<T> {

  private ArrayList<T> list = new ArrayList<T>();

  public void push(T obj) {
    list.add(obj);
  }

  public T pop() throws QueueException {
    if (size() == 0)
      throw new QueueException(
        "Tried to pop something from the queue, " +
        "when it was empty");
    T result = list.get(0);
    list.remove(0);
    return result;
  }

  public boolean isEmpty() {
    return list.isEmpty();
  }

  public int size() {
    return list.size();
  }

  public void clear() {
    list.clear();
  }
}

 上記のコードでは、このキュークラスのためにジェネリック型を1つ使うことを宣言しています。

public class Queue<T>

 ジェネリック型Tは、キューに格納されるクラスの型です。キューにアイテムを格納するために、このクラスはT型を受け取るArrayListも作成します。

 このクラスのpushメソッドは非常にシンプルです。ジェネリック型Tのオブジェクトを1つ受け取り、ArrayListに追加するだけです。

 popメソッドの方はもう少し複雑です。まず、キューからオブジェクトを1つポップしようとしたときにオブジェクトが存在しなければ、このクラスはQueueException型の例外をスローします。次に示すのが、QueueExceptionクラスです。

package com.heatonresearch.examples.collections;
public class QueueException extends Exception {
  public QueueException(String msg) {
    super(msg);
  }
}

 QueueExceptionをスローするコードは次のようになります。

if (size() == 0)
  throw new QueueException(
    "Tried to pop something from the queue, " + 
    "when it was empty");

 キューが空でない場合、このメソッドはキューから最後の要素を取り出し、resultという変数に格納したうえで、この要素をキューのリストから削除します。この処理を実装したのが、次のコードです。

T result = list.get(0);
list.remove(0);
return result;

 一時的な変数resultもまた、ジェネリック型Tのメンバになっていることに注意してください。ジェネリック型に実際のJavaの型を「代入」してこのクラスを使うときは、互換性のレベルを最大化するために、こうした変数の参照には必ずジェネリック型を使うことが重要です。

キュークラスの動作テスト

 次のクラスは、「ジェネリックな」キューの動作をテストするためのものです。

package com.heatonresearch.examples.collections;

public class TestQueue {

  public static void main(String args[]) {
    Queue<Integer> queue = new Queue<Integer>();
    queue.push(1);
    queue.push(2);
    queue.push(3);
    try {
      System.out.println("Pop 1:" + queue.pop());
      System.out.println("Pop 2:" + queue.pop());
      System.out.println("Pop 3:" + queue.pop());
    } catch (QueueException e) {
      e.printStackTrace();
    }
  }
}

 上記のコードで作られるキューは、Integerオブジェクトしか受け取りません。

Queue<Integer> queue = new Queue<Integer>();

 このテストでは、次に3つの整数をキューに追加します。

queue.push(1);
queue.push(2);
queue.push(3);

 キューに追加された数がプリミティブ型であることに注意してください。J2SEのオートボクシング機能のおかげで、プリミティブなint型が自動的にIntegerオブジェクトに変換されるのです。

 次に、popメソッドを使ってこのオブジェクトを取り出します。キューが空の場合は、QueueExceptionをキャッチします。このキューから3つの数をポップした結果は次のとおりです。

1
2
3

 ここではIntegerオブジェクトを受け取るキューを紹介しましたが、このキュークラスはジェネリック型なので、どんなJavaオブジェクトに対しても動作します。

先読み可能なスタックコレクションの作成

 ここでは、もっと複雑なコレクション型として、オブジェクトを実際に取り出す前に先読みが可能な(つまり、「こっそり参照できる」)スタックをとりあげます。反復子またはJ2SE 5.0の新しいfor each構文を使えば、先読みができます。

 ここで紹介するPeekableStackクラスは、先入れ後出し(FILO)方式のスタックであり、現在のスタックの中身に対する繰り返し処理を伴います。実装には2つのクラスを使用します。1つ目は、実際にスタックを実装するPeekableStackクラスです。2つ目は、「Java標準」のIteratorクラスを実装するPeekableStackIteratorクラスで、スタックに対する繰り返し処理を可能にします。リスト2は、PeekableStackクラスです。

リスト2 PeekableStackクラス
package com.heatonresearch.examples.collections;

import java.util.*;

public class PeekableStack<T> implements Iterable<T> {

  private int version;
  private ArrayList<T> list = new ArrayList<T>();

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public Iterator<T> iterator() {
    PeekableStackIterator peekableStackIterator = new 
      PeekableStackIterator(this, list);
    return peekableStackIterator;
  }

  public void push(T obj) {
    version++;
    list.add(obj);
  }

  public T pop() {
    // find the last element
    int last = list.size() - 1;

    // is the stack empty?
    if (last < 0)
      return null;

    // return the last element and remove it
    T result = list.get(last);
    list.remove(last);
    return result;
  }

  public int size() {
    return list.size();
  }
}

 リスト2のPeekableStackクラスは、Iterableインターフェイスを実装していることに注意してください。このインターフェイスは、J2SE 5.0の新しいfor each構文をサポートするために必要になります。Iterableインターフェイスにより、コレクションがiteratorメソッドをサポートしていることを明示します。iteratorメソッドは反復子を返すものです。このインターフェイスがなければ、使用するクラスはfor each構文に対応できません。

 先読み可能なスタックには、先ほどのキューと同じようにpushpopの両メソッドが含まれています。pushメソッドは、キューのときより少しだけ複雑になっています。このpushメソッドでは、スタックにオブジェクトを追加するとversionという変数の値をインクリメントします。

 このversion変数によって、PeekableStackIteratorクラスがスタックに変更を加えることを防止できます。反復子を作成すると、現在のバージョン番号のコピーがその反復子によって保持されます。pushメソッドの呼び出しによって、スタックになんらかの変更が生じると、バージョン番号が一致しなくなります。この不一致が起こると、反復子はConcurrentModificationExceptionをスローします。

 popメソッドはもう少し複雑です。最初に、このメソッドは取得したリストのサイズから1を引くことによって、リストの最後の要素のインデックスを求めます。

int last = list.size() - 1;

 この結果が0未満の場合は、スタックが空なので、popメソッドはnullを返します。

if (last < 0)
  return null;

 スタックに「最後の要素」が存在する場合は、リストからその要素を取り出します。この要素は、リストからの取り出しに成功した後で削除できます。

T result = list.get(last);
list.remove(last);

 最後に、リストから取り出したオブジェクトを返します。

return result;

 for eachによる繰り返しをサポートするために、PeekableStackクラスのiteratorメソッドは、「Java標準」のIteratorクラスを返します。Iteratorクラスを使えば、スタックに含まれるすべてのオブジェクトに対する繰り返し処理が可能です。このiteratorメソッドは、新しい反復子を作成して返します。

PeekableStackIterator peekableStackIterator = 
  new PeekableStackIterator(this, list);

 ご覧のとおり、この反復子のクラスは、コンストラクタの引数として現在のスタックとスタックが持つ要素のリストを受け取ります。これらの値は、PeekableStackIteratorクラスによって使用されるのですが、これについては次のセクションで説明します。

先読み可能スタックの反復子の作成

 Javaの新しいfor each構文を用いてPeekableStackクラスを利用する場合、「Java標準」の反復子を作成する必要があります。リスト3にPeekableStackIteratorクラスの実装を示します。

リスト3 PeekableStackIteratorクラス
package com.heatonresearch.examples.collections;

import java.util.*;

public class PeekableStackIterator<T> implements Iterator {

  private int version;
  private PeekableStack<T> source;
  private List<T> list;
  private int position;

  PeekableStackIterator(PeekableStack<T> source, List<T> list) {
    this.source = source;
    this.list = list;
    this.version = source.getVersion();
    this.position = 0;
  }

  public boolean hasNext() {
    if (version != source.getVersion())
      throw new ConcurrentModificationException();

    return (position < list.size());
  }

  public T next() {
    // check for concurrent modification
    if (version != source.getVersion())
      throw new ConcurrentModificationException();

    // if its empty then return null
    if (!hasNext())
      return null;

    // find the current position
    int last = list.size() - 1;
    last -= position;

    // if there are still elements left, return the
    // next one and update the current position.
    T result = null;

    if (last >= 0) {
      position++;
      result = list.get(last);
    }

    return result;
  }

  public void remove() {
  }

}

 リスト3の反復子が、スタックの値を実際に変更することは一切ありません。その代わりに、要素のリストにおける現在の位置を絶えず把握し、常に、その次の位置にある要素を返します。現在の要素の位置についての情報を反復子のクラス自体が保持しているので、同じスタックに対して複数の反復子を利用することも可能です。

 次のプログラムは、先読み可能スタックの動作をテストするためのものです。

package com.heatonresearch.examples.collections;

import java.util.*;

public class TestPeekableStack {

  public static void main(String args[]) {
    PeekableStack<Integer> stack = new 
      PeekableStack<Integer>();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    for (int i : stack) {
      System.out.println(i);
    }

    System.out.println("Pop 1:" + stack.pop());
    System.out.println("Pop 2:" + stack.pop());
    System.out.println("Pop 3:" + stack.pop());
  }
}

 ご覧のとおり、スタックに3つの要素を追加しています。その後、新しいfor each構文を用いて、この3つの要素の値を表示しています。

for( int i: stack)
{
  System.out.println( i );
}

 この記事では、J2SEの新仕様であるジェネリックとfor each構文をサポートしたコレクションを、そつなく実装する方法を紹介してきました。ご覧いただいたように、ジェネリックの活用と適切なインターフェイスの実装により、J2SE 5.0の新しい構文に対応したコレクションを作成するのは比較的容易です。こうしたコレクションクラスがシームレスに統合されてJ2SE 5.0に導入されていることがおわかりになったと思います。

著者紹介

Jeff Heaton(Jeff Heaton)
ライター、大学教員、コンサルタントとして活動中。4冊の著作があり、論文誌および雑誌で20を超える記事を発表。また、個人のWebサイトを管理し、人工知能とスパイダー/ボットプログラミングをはじめとする話題について情報発信を行っている。メールの宛先はjheaton@heatonresearch.com
関連テーマ
このエントリーを含むはてなブックマーク この記事をクリップ!
BuzzurlにブックマークBuzzurlにブックマーク Yahoo!ブックマークに登録
この記事をokyuuへインポート
最新トップニュース
データメーション
【データメーション】
中国が「Green Dam」フィルタ規制を撤回(7月1日)
Graphic Design Forum
【Graphic Design Forum】
Chris Dickman(6月25日)
プライバシー ジャパン・インターネットコム版
【プライバシー ジャパン・インターネットコム版】
グーグル・ストリートビューの問題について総務省の見解(6月23日)
エンジニアの独り言
【エンジニアの独り言】
システムを「使う」時代のエンジニアに求められるもの(6月2日)
最新ハイテク講座
最新ハイテク講座
電気は家庭でつくる時代へ!燃料電池「エネファーム」(7月3日)
アクセス解析で見るWebマーケティング
アクセス解析で見るWebマーケティング
決定力を探るアクセス解析(7月3日)
百式のネットビジネス研究
百式のネットビジネス研究
ファーストフードを高級っぽく盛り付けて紹介している「Fancy Fast Food」(7月3日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(7月2日)
成約率、反応率を上げる Web 文章術
成約率、反応率を上げる Web 文章術
言葉がダイレクトにキャッシュを生む(7月2日)
不況時代の Web ビジネス最適化講座
不況時代の Web ビジネス最適化講座
アクセス解析エキスパートここだけの話、Web コンシェルジュの“勉強法”こっそり教えます(7月2日)
「Webからの脅威」―その傾向と最新対策
「Webからの脅威」―その傾向と最新対策
不正プログラムの分類(7月1日)
DevX
DevX
JavaScriptとDOMによる動的なWebページの作成(6月30日)
エンジニア転職ノウハウ開発室
エンジニア転職ノウハウ開発室
今のままで大丈夫?3匹の子ブタ的キャリア危険度診断(6月30日)
アイレップの SEM フロンティア
アイレップの SEM フロンティア
Web サイトは「無駄な穴のたくさん開いたじょうご」〜サイト成果向上の基本的な考え方(6月30日)
Copyright 2009 Japan Internet.com K.K. All Rights Reserved.http://www.internet.com/