SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Javaで3つの値を持つMapを作成する

SQLのようなシンプルさを持つJavaマッピングの実装

2つのキーの使用

 多くの場合、ある型と別の型との値マッピングは、ドメインの一般的なナレッジに一致しません。例えば、時々獲物を捕ってくるハンターがいて、その「ハンターの名前」、狩猟が行われた「日付」、獲物の「大きさ」に関するデータを記録するものとします。SQLを使えばソリューションは明らかです。次のような定義を使用することになります。

CREATE TABLE Log (Hunter String, Time Timestamp, Weight Number);

 しかし、Javaでこれを表現するのは困難です。Hunterというクラスがあったとしても、この3つのデータ項目をどうすればリンクできるでしょうか。1つのソリューションは、特殊なクラスを作成することです。

class Event {
  Hunter hunter;
  Timestamp time;
  double mammothWeight; // won't fit in float
}

 この場合の欠点は、特定のハンターや特定の日付に関するファクトを見つける方法がないことです。Collection<Event>を用意するとしても、すべてをスキャンする必要があります。

 自然な、データベース的なソリューションでは、このようなイベントにインデックスを付けます。しかし、何がキーになるのでしょうか。J2EEでは、次のようなキーの特別クラスが考えられます。

class EventKey {
  Hunter hunter;
  Timestamp time;
}

 このようなキーは、Entity Beanを取得するには便利ですが、現実的な意味合いを持ちません。ハンターと日付は一体ではなく、ハンターはハンターであり、日付は日付です。さらに、このような「キー」を使用しても、指定のハンターの成功(または失敗)の履歴や、一族のお祝いや不幸の歴史を追跡することはできません。つまり、ハンターと日付でそれぞれ別のインデックスを使用する必要があります。解決しようとしている問題に応じて、1つまたは2つのインデックスを用意することになります。

Map<Hunter, Map<Timestamp, Event>> hunterIndex;
Map<Timestamp, Map<Hunter, Event>> timeIndex;

 ここで、Collection<Event>に加えてこの2つのマップを保持する場合に、どのような処理が必要になるかを考えてみましょう。Collection<Event>にイベントを追加するたびに、hunterIndexを参照し、エントリの有無を確認することが必要です。該当するエントリがない場合は、適切なエントリを空のマップで作成し、そのマップに新しいファクトを挿入します。削除についても同様です。この場合は、空のセカンダリマップを削除した方がよいか、またはそのままにしておいてもよいかを、同僚にたずねる必要もあります。自分ではその答えがわかっているつもりでも、あなたのコードをレビューする同僚は異なる答えを出すかもしれません。このような事態がいくつも出てきます。私は1年に何度もこのようなカスケードマップを作成しているので、こうした状況については嫌というほど知っています。

 実際、このようなカスケードマップを使用する人は、Collection<Event>を別に作成するという方法をめったに採用しません。なぜなら、コレクションを上から下に渡す場合や、コレクションのサイズをカウントし直す必要がある場合を除いて、時間と場所の無駄に思われるからです。このようなケースでは、現実的なプログラマは、2つの大きく異なる方法のいずれかを使用します。

  1. 要求が発生したらhunterIndexをスキャンし、セカンダリマップのサイズを調整する。
  2. 独立したカウンタを用意し、追加または削除のたびにカウンタを更新する。この場合、プログラマはスレッドと例外を考慮する必要があり、何ヶ月にもわたって実行し続けるアプリケーションが前提となる(さらに、再カウントの必要性はないものとする)。

 私の考えでは、このような事態になるのは、Javaプログラマが既存のクラスの観点で物事を考える傾向があり、java.utilやjava.langにあるものをそのまま使おうとするためです。Pythonプログラマの場合はこのような問題に出会うことはなく、JavaScriptプログラマには選択の余地がありません。JavaScriptプログラマにできることは、配列にキーとして文字列を関連付けることだけです。

SQLプログラマの場合

 SQLプログラマの場合は、Collection<Event>に相当するものを使用でき、さらにあらかじめ指定しておけば、変更時に自動的に更新されるインデックスも使用できます。おそらく、この動作は真似することができるでしょう。先ほどの例で示したインターフェイスMap<X,Y>に加えて、もう1つのキー(あくまでも1つだけ)を使用する新しいインターフェイスMap<X,Y,V>を使用したらどうなるでしょうか。Map<X,Y,V>のうち、XとYはキー型、Vは値型とします。以下のコードはMap<X,Y>とほとんど同じですが、エントリには2つのキーがあります。つまり、2つのキーセットがあります。

public interface Map2<X,Y,V> {
  int size();
  boolean containsKeyPair(Object key1, Object key2);
  V get(X key1, Y key2);
  V put(X key1, Y key2, V value);
  V remove(X key1, Y key2);
  Set<X> keySet1();
  Set<Y> keySet2();
  Collection<V> values();
  Map<Y,V> curry1(X key1);
  Map<X,V> curry2(Y key2);

  interface Entry<X,Y,V> {
    public X getKey1();
    public Y getKey2();
    public V getValue();
    V setValue(V value);
  }
  Set<Map2.Entry<X,Y,V>> entrySet();
}

 curry1curry2という2つのメソッドに注目してください。これらは1つのキーを受け取り、もう1つのキーと値とのマッピングを返します。curry1curry2という名前は、この処理を表す関数型プログラミング用語のカリー化(Currying)に由来します。

 デフォルトの実装のAbstractMap2<X,Y,V>は、エントリをセットに格納します。1組のキーに対応する値を検索するには、エントリセット全体をスキャンすることになります。エントリセットが小さければ、それほど問題ではないでしょう。ここでの唯一の抽象メソッドはSet<Map2.Entry<X,Y,V>> entrySet()であり、データを任意の方法で格納できます。

 デフォルトの実装はあまり効率的ではないので、インデックス付きのマップIndexedMap2<X,Y,V>を使用することにしましょう。このマップはXとYの2つのインデックスを保持し、Set<Map2.Entry<X,Y,V>> entrySet()は抽象メソッドのままです。

2つのパラメータから成るマップ

 カスケードマップを使用する場合にどのインデックスを最初にするか、またコレクション全体をスキャンするにはどうすればよいかという質問はよくありますが、これまで説明してきたように、これらの問題はいくつかのクラスを使って解決することができます。おなじみのMapおよびSetというクラスを適用するには、hunterを第一にしてtimeインデックス付きのイベントのコレクションを用意するか、timeを第一にしてhunterインデックス付きのイベントのコレクションを用意するかを指定します。しかし、2つのキーセットを持つマップであるMap2を適用する方が適切です。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Vlad Patryshev(Vlad Patryshev)

BorlandのJava Business UnitのR&Dエンジニア。先ごろmyjavatools.comプロジェクトの立ち上げに参加。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1235 2007/04/24 08:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー