Javaのコレクション入門

(注.この記事に限らず、Javaの記事では基本的にJava8以降を前提にします)
0. 前置き
Javaに限らず、プログラミングではデータをまとめておくのに配列だけではどうにもうまくいかないことが多々あります。そこでデータ構造が重要になってくるのですが、そのあたりの表現方法は言語によって様々です。
C++ならvectorとかlistとか標準ライブラリのコンテナが用意されていて、(あまり経験ないけど)C#だとLINQというものを使うようです。PHPだと配列でかなりのことが出来たりします(その分使い方には慎重さを要する)。Pythonだとリスト型やタプル、辞書などを用途に応じて使い分ける感じですね。

1. コレクションフレームワークとは
さて、Java。Javaでは、コレクションフレームワークというものを使います。コレクションフレームワークではリスト(値が順番に並んだもの)、セット(値が順番になっているとは限らないが、同じ値のものが1つだけのもの)、マップ(キーごとに値が対応したもの)といったデータを表現するのに必要なデータ構造が一通り揃っています(他にも両端からしか値を出し入れできないDequeというのもあるんですが、用途が限られるので今回は割愛)。

2. リスト
まずはリスト。リストは0から始まるインデックスごとにデータが入ったものです。Listインターフェースを元に実装されていて、ArrayList(可変長配列)LinkedList(連結リスト)の2つのクラスがよく使われます。ArrayListがC++でいうvectorみたいなものです。なお、JavaにもVectorクラスはあるんですが、こちらはJava8以降では非推奨になっています。
それぞれ、次のように定義します。

List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();

どちらのクラスで定義するかは、コンストラクタを呼び出す時に決まります。ちなみに、クラスとかっこの間の<>は、ダイヤモンド演算子といって、要素の型を省略する時の記法です。
よく使う操作は以下のような感じです。ArrayListに対しての記述になっていますが、LinkedListであっても行う処理は同じです。

List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();
arrayList.add("Apple");  // リストの最後に要素"Apple"を追加
arrayList.get(5);  // リストの5 + 1番目の要素を取得する
arrayList.set(3, "Orange");  // リストの3 + 1番目の要素の値を"Orange"に設定する
arrayList.remove(4); // リストの4 + 1番目の要素を削除し、後続のインデックスを1つ減らす
arrayList.size();  // リストの要素数を返す
arrayList.clear();  // リストを空にする
// arrayListの要素一覧を出力する
for (String data : arrayList) {
	System.out.println(data);
}

ただ、上で「行う処理は同じ」と書きましたが、ArrayListとLinkedListでは実装方法が違うので、それぞれ得意不得意があったりします。
getとかsetとかはそれぞれの要素がメモリ上でインデックスされているArrayListの方が速く、addとかremoveは要素間がお互いのリンク情報(C言語のポインタのようなイメージ。Javaにはポインタはないけど)で繋がっているLinkedListの方が高速だったりします。
なので、データベースとかからある程度大きなデータを持ってきて、その中の要素へのアクセスが主目的の場合はArrayListが、リストへのデータの追加や削除が頻繁に発生する場合はLinkedListが向いています。

3. セット
セットはリストと同様にデータをまとめたものですが、リストとは違ってインデックスは存在せず、また、同じ値のものは2つ以上持てないというものです。大学で数学を勉強した人は集合・位相の集合をイメージしてもらえればと思います。
こちらはSetインターフェースを元に実装されていて、HashSet、TreeSet、LinkedHashSetの3つがよく使われます。それぞれ、次のように定義します。

Set<String> hashSet = new HashSet<>();
Set<String> treeList = new TreeList<>();
Set<String> linkedHashSet = new LinkedHashSet<>();

基本的な操作は以下のような感じです。HashSetに対して書いていますが、他の2つでも同じです。getとsetがないところに気をつけてください。

Set<String> hashSet = new HashSet<>();
Set<String> treeSet = new TreeSet<>();
Set<String> linkedHashSet = new LinkedHashSet<>();
hashSet.add("Banana");  // 集合の要素に"Banana"が含まれていなければ追加する
hashSet.remove("Lemon"); // 集合に"Lemon"が含まれていたら削除する
hashSet.size();  // 集合の要素数を返す
// hashSetの要素一覧を出力する
for (String data : hashSet) {
	System.out.println(data);
}

HashSet、TreeSet、LinkedHashSetの違いですが、HashSetは処理が高速な代わりに順番が全く保証されず、TreeSetは要素がソートされた状態で保持され、LinkedHashSetは要素を追加した順番が保持されます。

treeSet.add("Banana");
treeSet.add("Orange");
treeSet.add("Apple");
for (String data : treeSet) {
	System.out.println(data);
}

だと、”Apple” -> “Banana” -> “Orange”の順に出力され、

linkedHashSet.add("Banana"); 
linkedHashSet.add("Orange");
linkedHashSet.add("Apple"); 
for (String data : linkedHashSet) {
	System.out.println(data);
}

だと、”Banana” -> “Orange” -> “Apple”の順番に出力されます。

4. マップ
マップは値だけでなく、値を指定するキーと一緒の組み合わせでデータを保持するものです。マップではキーは一意(同じものが複数存在しない)ですが、値は同じものが複数あってもいいです。
マップはMapインターフェースを元に実装されていて、HashMap、TreMap、LinkedHashMapの3つがよく使われます。それぞれ次のように定義します。

Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> treeMap = new TreeMap<>();
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();

基本的な操作は以下のようになります。putメソッドを使ってキーと値の組合せをマップに追加します。

Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> treeMap = new TreeMap<>();
Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
hashMap.put("Tomato", 300);  // マップのキー"Tomato"の値を300に設定する
hashMap.get("Tomato");  // マップのキーが"Tomato"のものの値を返す(なければnull)
hashMap.remove("Lemon"); // マップにキーが"Lemon"のものが含まれていたら削除する
hashMap.size();  // マップの要素数を返す
// マップの要素ごとにキー:値という形で出力する
for (Map.Entry<String, Integer> data : hashSet.entrySet()) {
	System.out.println(data.getKey() + ":" + data.getValue());
}

こちらも、セットと同様HashMapは高速だけど順番が保証されない、TreeMapはキーを順番としてデータを保持、LinkedHashMapはデータを追加した順番に保持されます。

5. 終わりに
Javaで通常よく使うコレクションについて、駆け足で紹介してみました。他にもいくつかあるようですが、今回紹介したものを用途に応じて使いこなせれば、Javaでのデータの扱いで困ることはそんなにはないと思います。
自分は以前、名前とあるオブジェクトの組み合わせのデータをHashMapで定義していたのですが、途中で名前の昇順に出力する必要があることに気がついて、あわててTreeMapに書き直したことがありました。皆さんもコレクションを使うときは何を目的にしているかを考えて、最適なものを選択するようにしてください。