この記事について
Java基礎 第16回ではJavaのパッケージを紹介しました。
この記事は、Javaのコレクションフレームワークとその主要なインターフェース(List、Set、Map)及びそれぞれの一般的な実装(ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap)について、概要と使用状況を詳しく説明しています。各インターフェースの特性と使用例から、各実装のパフォーマンスや適用シチュエーションを比較し、読者が自身の要求に適した『 コレクション 』を選ぶ助けとなる内容になっています。
この記事はJavaの初級から中級のプログラマー、あるいはJavaのコレクションフレームワークについて学びたいプログラマーにとって非常に役立ちます!
はじめに
Javaのコレクションフレームワークは、さまざまな種類のデータを効率的に管理するための一連のクラスとインターフェースを提供します。このフレームワークは、データの管理と操作を簡単にし、より強固で柔軟なコードの作成を可能にします。
具体的には、java.utilパッケージに含まれるList, Set, Mapの3つの主要なインターフェースと、それぞれに対応する実装クラスを提供しています。これらのインターフェースとクラスは、データの一覧表示、一意性の確保、キーと値の関連付けといったさまざまな要件に対応します。
Listについて
Listインターフェースは、データを順序付けして格納することを可能にします。また、重複する要素を許します。この特性は、順序が重要で、重複する可能性のある要素を扱うシチュエーションにおいて有用です。例えば、人々の名前をアルファベット順にリスト化したり、同じ名前の人が複数いる場合にもそれぞれをリストに含めることができます。
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("Alice"); // リストなので重複が許される
System.out.println(names); // [Alice, Bob, Charlie, Alice]
ArrayList
ArrayListはListの最も一般的な実装で、内部的には配列を使用しています。そのため、インデックスを指定して要素を取得する際の速度は非常に速いです。しかし、要素を追加または削除する際には内部的な配列のコピーが発生するため、その操作が遅くなる可能性があります。つまり、要素の検索が多い場合や、要素の追加や削除が少ない場合にはArrayListが有効です
Copy code
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
arrayList.add(i);
}
System.out.println(arrayList.get(500000)); // インデックス指定の取得は高速
LinkedList
一方、LinkedListはListの別の実装で、内部的には双方向リンクリストを使用しています。リンクリストの特性上、要素の追加や削除には速さが一定(特にリストの先頭や末尾での操作が速い)ですが、特定のインデックスでの要素の取得には時間がかかります。これは、要素を取得するためにはリンクをたどっていく必要があるからです。したがって、要素の追加や削除が頻繁に発生する場合や、特にリストの両端での操作が多い場合にはLinkedListが適しています。
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
linkedList.add(i);
}
System.out.println(linkedList.get(500000)); // インデックス指定の取得は遅い
Setについて
次に、Setインターフェースです。Setは、重複する要素を持たないコレクションを表現します。すなわち、各要素が一意であることが保証されます。この特性は、一意性が必要な場合や、要素の存在をテストする目的で使用されます。例えば、選挙の投票者のリストや、オンラインゲームのプレイヤー名など、一意でなければならないデータを管理する際にSetは役立ちます。
Set<String> namesSet = new HashSet<>();
namesSet.add("Alice");
namesSet.add("Bob");
namesSet.add("Charlie");
namesSet.add("Alice"); // 重複する要素は無視される
System.out.println(namesSet); // [Alice, Bob, Charlie]
HashSet
HashSetは、一意な要素を保持するための最も一般的なSetの実装で、内部的にはハッシュテーブル(キーと値のペアを格納するデータ構造)を使用しています。そのため、要素の追加、削除、検索といった操作は一定の速さで行われます。しかし、要素はハッシュ値に基づいて格納されるため、特定の順序で要素を取得することはできません。順序が重要でない場合、またはデータの一意性が必要な場合にHashSetを使用します。
Set<Integer> hashSet = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
hashSet.add(i);
}
System.out.println(hashSet.contains(500000)); // 要素の検索は高速
TreeSet
TreeSetはSetの別の実装で、要素をソートされた状態で格納します。内部的には赤黒木(一種のバランス型二分探索木)を使用しています。そのため、要素の追加や削除、検索には時間がかかります。しかし、ソートされた順序での要素の取得は高速です。ソートされた要素を必要とする場合、または要素の範囲検索を行う場合にはTreeSetを使用します。
Set<Integer> treeSet = new TreeSet<>();
for (int i = 0; i < 1000000; i++) {
treeSet.add(i);
}
System.out.println(treeSet.first()); // 最初の要素を取得
System.out.println(treeSet.last()); // 最後の要素を取得
Mapについて
最後に、Mapインターフェースです。Mapは、キーと値のペアを格納するコレクションを表します。キーは一意でなければならず、各キーは単一の値に対応します。この特性は、一対一の関連付けや辞書のようなデータ構造を表現する際に使用されます。例えば、電話番号帳(名前と電話番号のペア)、辞書(単語と定義のペア)、都市と人口の対応表など、キーと値のペアを管理する必要がある場合にMapは使用されます。
Map<String, Integer> populationMap = new HashMap<>();
populationMap.put("Tokyo", 37797500);
populationMap.put("Delhi", 30939900);
populationMap.put("Shanghai", 27058400);
System.out.println(populationMap.get("Tokyo")); // Tokyoの人口を取得
HashMap
HashMapは、キーと値のペアを保持するための最も一般的なMapの実装です。内部的にはハッシュテーブルを使用しています。そのため、キーを使用した要素の追加、削除、検索といった操作は一定の速さで行われます。しかし、要素はハッシュ値に基づいて格納されるため、特定の順序で要素を取得することはできません。順序が重要でない場合、または高速なキーに基づく操作が必要な場合にHashMapを使用します。
Map<Integer, String> hashMap = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
hashMap.put(i, "Value" + i);
}
System.out.println(hashMap.get(500000)); // キーを使用した取得は高速
TreeMap
TreeMapは、キーがソートされた状態でキーと値のペアを保持するMapの実装です。内部的には赤黒木を使用しています。そのため、キーによる要素の追加、削除、検索には時間がかかります。しかし、ソートされた順序での要素の取得は高速です。ソートされた要素を必要とする場合、または要素の範囲検索を行う場合にはTreeMapを使用します。
Map<Integer, String> treeMap = new TreeMap<>();
for (int i = 0; i < 1000000; i++) {
treeMap.put(i, "Value" + i);
}
System.out.println(treeMap.firstKey()); // 最初のキーを取得
System.out.println(treeMap.lastKey()); // 最後のキーを取得
コレクションのパフォーマンス比較
以下の表は、それぞれのコレクションが提供する主要な操作のパフォーマンス特性を示しています。数値は大きいほど、該当の操作に時間がかかることを示します(1が最も高速、3が最も遅い)。
この表を理解することは、具体的な要件とパフォーマンス特性に基づいて最適なコレクションを選択するのに役立ちます。たとえば、要素の追加と検索を頻繁に行うアプリケーションでは、HashSetやHashMapが適しています。一方、順序付けされた要素が必要で、要素の追加や削除が少ない場合は、ArrayListやTreeSetを考慮することができます。
なお、これらの数値はあくまで一般的なガイドラインであり、具体的なパフォーマンスは使用するJVMやデータの特性により変わる可能性があることを覚えておきましょう。
まとめ
コレクションフレームワークは、Javaプログラムでデータを効率的に扱うための強力なツールです。それぞれのコレクションが持つ特性とパフォーマンス特性を理解し、適切に使用することで、より良いコードを書くことができます。
今回の内容は実現場で多用されるスキルです。様々なケースを想定して自分自身でもコードを書いてみましょう。
次回はJavaにおける日付について紹介していきます
お楽しみに!
コメント