J2SE 1.4では、Java Collections FrameworkにLinkedHashSet とLinkedHashMap の2つの新たな実装を取り入れています。これらを追加する利点は、ハッシュ・コレクションがその要素に対して2種類のたどり方を保持できるということです。標準のハッシュ関係に加えて、コレクション全体を横断するリンクによるリストが備わります。通常では、この新しい2番目のたどり方は挿入順序に従います。つまり、コレクションの反復子が要素を(ハッシュ・コードがこれらをコレクションに組み込んだ順序ではなく)その挿入順に返すことになります。しかし、LinkedHashMapは2つめの順序付けオプションをサポートします。つまり挿入順ではなく、アクセス順にリンクによるリストを保持します。
では、これらの新しいクラスがどのように機能するか見てみましょう。
これらの新しいクラスを試すのは、簡単です。java.util パッケージをインポートして、ここで使う項目の集合を見つけるだけです。この例では暦月を使用することにします。セットで作業する場合は英語の月名を使用し、マップで作業する場合は英語とイタリア語の月名を使用します。
リスト1. クラス定義の開始
import java.util.*;
public class OrderedAccess {
public static void main(String args[]) {
String months[] = new DateFormatSymbols().getMonths();
String italianMonths[] = new DateFormatSymbols(Locale.ITALIAN).getMonths();
}
}
|
ここでは、皆さんがすでに英語の月名と順序をご存じのものと想定します。イタリア語の月名になじみのない皆さんのためにここで紹介しておきますが、Gennaio、Febbraio、Marzo、Aprile、Maggio、Giugno、Luglio、Agosto、Settembre、Ottobre、Novembre、Dicembreとなります。ただし、getMonths() が返す月名は大文字では表示されません。
LinkedHashSet は基本HashSet クラスのサブクラスです。したがってHashSet で行えることは、すべてLinkedHashSet で行うことができます。このクラスには新たに追加されたメソッドはありません。次の4つのコンストラクターがすべてです。
-
LinkedHashSet() -
LinkedHashSet(Collection c) -
LinkedHashSet(int initialCapacity) -
LinkedHashSet(int initialCapacity, float loadFactor)
セットに要素を追加する場合、要素ごとにadd() を呼び出すことも、あるいはCollection を作成してこれをコンストラクターに渡すこともできます。ここでは配列に要素があるので、最も簡単な手段はArrays.asList() を利用することです。これは、元の配列順序を保ったまま、配列をラップしてList にして返します。コンストラクターにこのリストを渡すことによって、同じリストをLinkedHashSet と簡易HashSet の両方に容易に追加することができます。
リスト2. セットの充てん
List list = Arrays.asList(months);
Set orderedSet = new LinkedHashSet(list);
Set unorderedSet = new HashSet(list);
|
セットを充てんすると、リンクによるセットが要素の挿入順序を保持しているかどうかその要素について確かめて、その結果を標準のハッシュ・セットの場合と比較することができます。iterator() を通じてセットごとに一つ一つ繰り返すことも、あるいは本来この作業を行ってくれるtoString() メソッドを(暗黙的に)ただ呼び出すこともできます。
リスト3.セット結果の表示
System.out.println("Ordered: " + orderedSet);
System.out.println("Unordered: " + unorderedSet);
|
順序付けられたセットは次のように表示されます。
リスト4. 順序付きセット結果の表示
Ordered: [January, February, March, April, May, June, July, August, September, October, November, December, ]
|
一方、順序なしセットは次のように表示されます。
リスト5. 順序なしセット結果の表示
Unordered: [March, April, November, October, January, July, September, February, December, May, June, August, ]
|
LinkedHashMap は、基本的にはLinkedHashSet と同様に機能しますが、要素ごとにキーと値が必要になります。これはさらに、オリジナル・クラス(この場合HashMap)からサブクラス化しますが、コンストラクターは5つになります。
-
LinkedHashMap() -
LinkedHashMap(int initialCapacity) -
LinkedHashMap(int initialCapacity, float loadFactor) -
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) -
LinkedHashMap(Map)
新たに加わったコンストラクターは、アクセス順序のオプションを扱います。アクセス順序のデフォルトは、falseです。リストをアクセス順にするためには、true を渡す必要があります。つまりマップの先頭は、マップ・エントリーのうち最も前に使われたものになります。
通常のHashMap に比べてLinkedHashMap が優れている点は、繰り返しの回数がマップの容量に影響されないことです。大きい容量を選択した場合、通常のHashMap ではパフォーマンスに影響しますが、LinkedHashMap では繰り返し横断回数にまったく影響しません。
要素のペアごとにput() を行う必要があるという理由から、マップへの要素の追加は、セットに追加する場合よりもいくぶん複雑になります。以下のコードには特別なものはなく、Map を単にコンストラクターに渡してしまう代わりに、月名をループさせているだけです。
リスト6.マップの充てん
Map orderedMap = new LinkedHashMap();
Map unorderedMap = new HashMap();
for (int i=0, n=months.length; i < n; i++) {
orderedMap.put(months[i], italianMonths[i]);
unorderedMap.put(months[i], italianMonths[i]);
}
|
ありがたいことに、セットと同様に、マップのtoString() メソッドを呼び出すだけで挿入順にマップ・エントリーを取り出すことができます。これはキーと値の各ペアをkey=value として返します。
リスト7.マップ結果の表示
System.out.println("Ordered: " + orderedMap);
System.out.println("Unordered: " + unorderedMap);
|
順序付けられたマップは次のように表示されます。
リスト8. 順序付きマップ結果の表示
Ordered: {January=gennaio, February=febbraio, March=marzo, April=aprile, May=maggio,
June=giugno, July=luglio, August=agosto, September=settembre, October=ottobre, November=novembre,
December=dicembre, =}
|
一方、順序なしマップは次のように表示されます。
リスト9. 順序なしマップ結果の表示
Unordered: {August=agosto, July=luglio, November=novembre, June=giugno, October=ottobre,
April=aprile, May=maggio, March=marzo, January=gennaio,
February=febbraio, =, December=dicembre, September=settembre}
|
自分でチェックするために、繰り返すことができます。反復子はすべて挿入順序を認識するので、values反復子 (values())を受け取った場合、次のように挿入順に値を確認することができます。
リスト10. 値の繰り返し
Collection values = orderedMap.values();
for (Iterator i = values.iterator(); i.hasNext(); System.out.println(i.next()));
|
これを表示すると、次のようになります。
リスト11. 順序付き値の表示
gennaio
febbraio
marzo
aprile
maggio
giugno
luglio
agosto
settembre
ottobre
novembre
dicembre
|
新たなクラスの特徴の最後として、LinkedHashMap のアクセス順序のオプションを調べてみましょう。true をLinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) コンストラクターに渡すことによって、最も古く使用したものから直近に使用したものへと、アクセス順にマップのリンクによるリストを保持できるようになります。言い換えれば、新しいアイテムは最後に追加され、マップへの検索がアイテムをリンクによるリストの最後に移動するということです。この最後の点が重要です。通常のマップの読み取りアクセスが順序を変えるので、複数のスレッドがマップからの読み取りを行うような場合は、アクセスを同期化する必要があります。
では実際に月をいくつか検索して、新たな順序を出力してみましょう。
リスト12. アクセス順に要素を検査
Map accessorderedMap = new LinkedHashMap(20, .80f, true);
for (int i=0, n=months.length; i < n; i++) {
accessorderedMap.put(months[i], italianMonths[i]);
}
accessorderedMap.get("June");
accessorderedMap.get("April");
accessorderedMap.get("February");
System.out.println(accessorderedMap);
|
3つの月にアクセスしたので、これらはリストでDecemberの後の終わりの部分に移動し、Februaryを最後にしてアクセス順に並びます。
リスト13. アクセス順序結果の表示
{January=gennaio, March=marzo, May=maggio, July=luglio, August=agosto, September=settembre,
October=ottobre, November=novembre, December=dicembre, =, June=giugno, April=aprile,
February=febbraio}
|
注:リンクしたマップに余分な要素があることにお気付きでしょう。=は、getMethods() によって返される特別なエントリーです。何らかの理由でgetMonths() は、12ではなく13の月を返し、最後の月には名前がありません。同様の理由から、リンクによるセットの例には最後の要素がないのにコンマがあります。
追加の注:LinkedHashMap の1.4ベータ2バージョンは、プロテクトされたremoveEldestEntry() メソッドを追加します。サブクラスは、たとえばマップがn 個の要素を超えないようにするために最も古いノードを削除すべきであれば、このメソッドにtrueを返させることができます。
それではソースの完結した例を紹介します。
リスト14. 完結した例
import java.util.*;
import java.text.*;
public class OrderedAccess {
public static void main(String args[]) {
String months[] = new DateFormatSymbols().getMonths();
String italianMonths[] = new DateFormatSymbols(Locale.ITALIAN).getMonths();
List list = Arrays.asList(months);
Set orderedSet = new LinkedHashSet(list);
Set unorderedSet = new HashSet(list);
System.out.println("Ordered: " + orderedSet);
System.out.println("Unordered: " + unorderedSet);
Map orderedMap = new LinkedHashMap();
Map unorderedMap = new HashMap();
for (int i=0, n=months.length; i < n; i++) {
orderedMap.put(months[i], italianMonths[i]);
unorderedMap.put(months[i], italianMonths[i]);
}
System.out.println("Ordered: " + orderedMap);
System.out.println("Unordered: " + unorderedMap);
Collection values = orderedMap.values();
for (Iterator i = values.iterator(); i.hasNext(); System.out.println(i.next()));
Map accessorderedMap = new LinkedHashMap(20, .80f, true);
for (int i=0, n=months.length; i < n; i++) {
accessorderedMap.put(months[i], italianMonths[i]);
}
accessorderedMap.get("June");
accessorderedMap.get("April");
accessorderedMap.get("February");
System.out.println(accessorderedMap);
}
}
|
- 本記事で使用したソース・ファイル をダウンロードしてください。
- 1.4リリースにおけるCollections Framework の変更内容について理解を深めてください。
-
javadoc を利用して、
LinkedHashSetの使用法を学習してください。 -
javadoc を利用して、
LinkedHashMapの使用法を学習してください。 -
developerWorks Javaテクノロジー・ゾーン でさらに多くのJava参考文献をご覧ください。

John Zukowskiは、JZ Ventures, Inc.の戦略的Javaコンサルティングを推進し、数多くのjGuruのコミュニティー主導のJava FAQsで常任指導者として活躍しています。最新の著書には、Apressから出版された「 Java Collections」および「 Definitive Guide to Swing for Java 2」 (第2版) があります。彼のメール・アドレスはjaz@zukowski.net です。