今月も引き続きjava.utilパッケージの新機能を紹介していきます。
今週と来週は新たに追加されたマップとセット、NavigableMapインタフェースとNavigableSetインタフェースを紹介していきます。
マップを使っている時、どのようなキーが使われているか分からない場合がありませんか。
どのようなキーが使われているか分からないので、筆者はキーの一覧を取得して、そこからイテレーションで回して、目的のキーを選ぶということをやってしまいます。しかし、これはマップの性質をまったく活かしていません。
たとえば、英和辞書を保持しているマップがあったとします。キーが英単語で、バリューがその単語の意味だとします。
そこからsupercalifragilisticexpialidocious注1の意味を調べようとしたとしましょう。でも、supercalifragilisticexpialidociousのつづりがよく分かりません。とりあえずsuperからはじまる英単語を調べてみようとしても、super以外はsuperではじまる英単語を直接マップから取得することができません。
このようにキーが直接分からない場合などに使えるのが、java.util.NavigableMapインタフェースです。
さっそく、サンプルでその動作を調べていきましょう。
サンプルのソース NavigableMapSample.java
NavigableMapインタフェースを実装したクラスには、TreeMapクラスとスレッドセーフなConcurrentSkipListMapクラスがあります。サンプルではTreeMapクラスを使用していきます。
NavigableMapインタフェースは表1に示したような、指定されたキーに最も近いキーを返すメソッドが定義されています。
メソッド名 | 説明 |
---|---|
lowerKey(key) | 指定されたキーよりも小さいキーを返します |
floorKey(key) | 指定されたキーと等しいか小さいキーを返します |
ceilingKey(key) | 指定されたキーと等しいか大きいキーを返します |
higherKey(key) | 指定されたキーよりも大きいキーを返します |
lowerEntry(key) | 指定されたキーよりも小さいキーに対応するMap.Entryオブジェクトを返します |
floorEntry(key) | 指定されたキーと等しいか小さいキーに対応するMap.Entryオブジェクトを返します |
ceilingEntry(key) | 指定されたキーと等しいか大きいキーに対応するMap.Entryオブジェクトを返します |
higherEntry(key) | 指定されたキーよりも大きいキーに対応するMap.Entryオブジェクトを返します |
これらのメソッドは指定したキーの小さい、もしくは大きいキーを選び出します。このためキーがソートされている必要があり、NavigableMapSampleインタフェースはSortedMapインタフェースの派生インタフェースとなっています。また、キーに相当するものがなかった場合はnullが返ります。
実際にこれらのメソッドを使ってみます。まずマップを用意します。
NavigableMap<String, String> map = new TreeMap<String, String>(); map.put("E", "Echo"); map.put("M", "Mike"); map.put("A", "Alpha"); map.put("S", "Sierra"); map.put("K", "Kilo"); map.put("I", "India"); map.put("Q", "Quebec"); map.put("C", "Charlie"); map.put("G", "Golf"); map.put("O", "Oscar");
lowerKeyメソッド、floorKeyメソッドを使ってみましょう。
// キーがマップに含まれている場合 System.out.println("lower G: " + map.lowerKey("G")); System.out.println("floor G: " + map.floorKey("G")); // キーがマップに含まれていない場合 System.out.println("lower F: " + map.lowerKey("F")); System.out.println("floor F: " + map.floorKey("F"));
この部分に対応する出力は次のようになりました。
lower G: E floor G: G lower F: E floor F: E
キーがマップに含まれている場合、floorKeyメソッドは自分自身を帰すため、lowerKeyメソッドの戻り値とは異なります。逆にキーがマップに含まれていなければ、双方のメソッドとも、指定されたキーに最も近いキーを返すため、値は同じになっています。
ceilingKeyメソッド、higherKeyメソッドでもそれは同じです。
// キーがマップに含まれている場合 System.out.println("cieling I: " + map.ceilingKey("I")); System.out.println("heigher I: " + map.higherKey("I")); // キーがマップに含まれていない場合 System.out.println("cieling J: " + map.ceilingKey("J")); System.out.println("heigher J: " + map.higherKey("J"));
この部分に対応する出力を次に示します。
cieling I: I heigher I: K cieling J: K heigher J: K
ここで示したように指定されたキーの次を示すことができるため、これらのメソッド群はナビゲーションメソッドと呼ばれます。インタフェース名のNavigableもナビゲーションが可能なところから付けられたようです。
また、NavigableMapインタフェースではSortedMapインタフェースのheadMapメソッド、tailMapメソッド、subMapメソッドも強化されています。
これらのメソッドはすべてSortedMapオブジェクトが戻り値となっています。そこで、新たにNavigableMapオブジェクトを返すメソッドがオーバロードされています。
引数が1つ増加し(subMapメソッドは2つ)、指定されたキーを含むか含まないかを指定することができるようになりました。また、オーバロードされたメソッドは戻り値の型がNavigableMapインタフェースになっています。
System.out.println("headMap: Eを含まない"); System.out.println(map.headMap("E", false)); System.out.println("headMap: Eを含む"); System.out.println(map.headMap("E", true)); System.out.println("tailMap: Oを含まない"); System.out.println(map.tailMap("O", false)); System.out.println("tailMap: Oを含む"); System.out.println(map.tailMap("O", true)); System.out.println("subMap: K, Oを含まない"); System.out.println(map.subMap("K", false, "O", false)); System.out.println("subMap: K, Oを含む"); System.out.println(map.subMap("K", true, "O", true));
実行結果を次に示します。
headMap: Eを含まない {A=Alpha, C=Charlie} headMap: Eを含む {A=Alpha, C=Charlie, E=Echo} tailMap: Oを含まない {Q=Quebec, S=Sierra} tailMap: Oを含む {O=Oscar, Q=Quebec, S=Sierra} subMap: K, Oを含まない {M=Mike} subMap: K, Oを含む {K=Kilo, M=Mike, O=Oscar}
実行結果からも分かるように、キーを含むか含まないかを明確に指定できるようになりました。
NavigableMapインタフェースの応用例
NavigableMapインタフェースの使い方は分かったので、辞書的な使い方を試してみることにしましょう。
サンプルのソース ZIPNumberSearcher.java
ZIPといってもファイル圧縮ではなくて、郵便番号です。ここではキーを郵便番号、バリューを住所としたマップを用意しました。といっても、東京のごくごく一部の郵便番号しか登録していませんが。
private NavigableMap<String, String> init() { NavigableMap<String, String> dic = new TreeMap<String, String>(); // 東京の郵便番号 dic.put("102-0083", "千代田区一番町"); dic.put("100-0013", "千代田区霞が関"); dic.put("104-0061", "中央区銀座"); ......
このマップで郵便番号を検索してみましょう。単にgetメソッドをコールした場合、そのキーがなければnullになってしまいます。
そこで、ceilingKeyメソッドやfloorKeyメソッドを組み合わせてみました。以下のコードでは150-0043を調べています。
NavigableMap<String, String> dic = init(); // 1500043 に最も近い番号を調べる String key = "150-0043"; if (dic.containsKey(key)) { // キーが登録されていた場合 System.out.printf("%s に対応する郵便番号は %d です%n", key, dic.get(key)); } else { // キーが登録されていなかった場合 String lowerKey = dic.lowerKey(key); String higherKey = dic.higherKey(key); System.out.printf("%s に近い郵便番号%n", key); if (lowerKey != null) { System.out.printf("%s(%s)%n", dic.get(lowerKey), lowerKey); } if (higherKey != null) { System.out.printf("%s(%s)%n", dic.get(higherKey), higherKey); } }
まずcontainsKeyメソッドでkeyがマップに登録されているかをチェックします。もし、登録されていればそのまま出力します。
登録されていなければ、keyの前後のキーを取り出し、それを出力します。この前後のキーを取得するのに、赤字で示したようにlowerKeyメソッドおよびhigherKeyメソッドを使用します。それらのキーに対応するバリューは青字で記述したようにgetメソッドで取得することができます。
keyより小さいキーがなかった場合、もしくはkeyより大きいキーがなかった場合、nullが返ります。そのため、nullかどうかのチェックを忘れないようにします。
出力結果を以下に示します。
150-0043 に近い郵便番号 渋谷区宇田川町(150-0042) 渋谷区松濤(150-0046)
実際には150-0043に相当するのは渋谷区道玄坂なのですが、このマップには登録されていなかったようです。そこで、その番号に近い150-0042と150-0046が表示されます。
ついでに登録されている150で始まる郵便番号も出力しておきましょう。
// 150 で始まる郵便番号を抽出 NavigableMap<String, String> subdic = dic.subMap("150-0000", true, "150-9999", true); // これでも同じ // NavigableMap<String, String> subdic // = dic.tailMap("150-0000", true); // subdic = subdic.headMap("150-9999", true); System.out.printf("%n150 番台の郵便番号%n%s", subdic);
マップの部分マップを取得するには、前述したようにheadMapメソッド、tailMapメソッド、subMapメソッドを使用します。
ここではsubMapメソッドを使用しましたが、headMapメソッドとtailMapメソッドを組み合わせても同様の結果を得ることができます。
150 番台の郵便番号 {150-0042=渋谷区宇田川町, 150-0046=渋谷区松濤}
結局、2つの番号しか登録されていなかったようです。これじゃ、辞書として使い物にならないですね。
さて、来週はNavigableMapインタフェースと同様にナビゲーションが可能なセットについて紹介します。
注1 映画Mary Poppinsで言及される最も長い英単語。実際にはこれより長い英単語があるらしいです。
著者紹介 櫻庭祐一 横河電機 ネットワーク開発センタ所属。Java in the Box 主筆 今月の櫻庭食欲の秋、読書の秋、スポーツの秋と秋は何をするにも最適な季節です。そして、秋はイベントの季節でもあります。 櫻庭が幹事をしている日本Javaユーザグループ(JJUG)でも11月6日にクロスコミュニティカンファレンスを開催します。場所は東京国際フォーラム、参加費は無料です。 また、11月6日から8日までSun Tech Days 2007 in Tokyoが同じく東京国際フォーラムで開催されます。基調講演はJavaの父 James Gosling氏。これは聞き逃せません。 みなさまお誘いあわせの上、ぜひご参加ください。 |