コレクションフレームワーク 第2回 代表的な関連クラス

s.arakawa

前回ではコレクションフレームワークの大本のインターフェースである、java.util.Collectionについて紹介しました。今回はCollectionに関わりのあるデータ構造をもう少し具象化して、List, Set, Map について紹介します。

これらのデータ構造は以下のように特徴付けられています。

データ構造 特徴
List 順序付けられたデータの集まりを保存する
Set 重複を許さないデータの集まりを保存する
Map キーと値のペアを保存する

List

java.util.Listは順序つきのリストを表現するデータ構造で、次のような実装クラスを持ちます。

このコレクションは、主に可変長配列の一つの実現方法として使用する場面が多いと思われます。
また、java.util.List自体はインターフェースなので、使用する際は実装クラスをインスタンス化しましょう。

List特有のメソッド

java.util.Listはjava.util.Collectionをスーパーインタフェースに持ちますが、List特有のメソッドがいくつか追加されています。

add(int index, Object o)

指定したインデックスにオブジェクトを挿入する。

import java.util.List;
import java.util.LinkedList;

public class C2S1 {
  public static void main(String[] args) {
    List l = new LinkedList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.add(1, "*inserted*");
    System.out.println(l.toString()));
  }
}
> java C2S1
[hoge, *inserted*, foo, bar]

get(int index)

指定したインデックスのオブジェクトを取得する。

import java.util.List;
import java.util.ArrayList;

public class C2S2 {
  public static void main(String[] args) {
    List l = new ArrayList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    System.out.println(l.get(1));
  }
}
> java C2S2
foo

set(int index, Object element)

指定したインデックスにオブジェクトを格納する。

import java.util.List;
import java.util.ArrayList;

public class C2S3 {
  public static void main(String[] args) {
    List l = new ArrayList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.set(1, "moge");
    System.out.println(l.toString());
  }
}
> java C2S3
[hoge, moge, bar]

indexOf(Object o)

指定したオブジェクトが格納されている先頭のインデックスを取得する。
指定したオブジェクトがリストの中に存在しない場合、-1を返す。

import java.util.List;
import java.util.ArrayList;

public class C2S4 {
  public static void main(String[] args) {
    List l = new ArrayList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    System.out.println("index of foo = " + l.indexOf("foo"));
    System.out.println("index of moge = " + l.indexOf("moge"));
  }
}
> java C2S4
index of foo = 1
index of moge = -1

lastIndexOf(Object o)

指定したオブジェクトが格納されている末尾のインデックスを取得する。
指定したオブジェクトがリストの中に存在しない場合、-1を返す。

import java.util.List;
import java.util.ArrayList;

public class C2S5 {
  public static void main(String[] args) {
    List l = new ArrayList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.add("foo");
    System.out.println("index of bar = " + l.indexOf("bar"));
    System.out.println("last index of bar = " + l.lastIndexOf("bar"));
    System.out.println("index of foo = " + l.indexOf("foo"));
    System.out.println("last index of foo = " + l.lastIndexOf("foo"));
  }
}
> java C2S5
index of bar = 2
last index of bar = 2
index of foo = 1
last index of foo = 3

subList(int fromIndex, int toIndex)

リストの部分リストを取得する。
コピーではなくビューを返すので、部分リストに行った変更は元のリストにも反映される。

import java.util.List;
import java.util.ArrayList;

public class C2S6 {
  public static void main(String[] args) {
    List l = new ArrayList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.add("hogehoge");
    l.add("foofoo");
    l.add("barbar");
    List subl = l.subList(2, 5);
    subl.set(0, "*replaced*");

    System.out.println("list = " + l.toString());
    System.out.println("sub list = " + subl.toString());
  }
}
> java C2S6
list = [hoge, foo, *replaced*, hogehoge, foofoo, barbar]
sub list = [*replaced*, hogehoge, foofoo]

各実装クラスの使い分け

実装クラス 使用する場面
java.util.ArrayList 基本的にはこのクラスを使用する
java.util.LinkedList 挿入や削除を頻繁に行う場合
java.util.Vector ほとんどの場面で使用しない

java.util.Vectorを使用しないのは、このクラスがレガシーコレクションと呼ばれる古い実装であるからです。
他の2個と違いVectorだけはスレッドセーフですが、それを理由にこのクラスの使用を考えるのはやめておいたほうが無難です。

ArrayList

Listの実装のうち、通常の配列を用いた実装で、特別重要なメソッドは存在しません。
ArrayList以外のクラスを使用する特別な理由がない限り、ArrayListを使用するのが無難です。

LinkedList

Listの実装のうち、リンクリストを用いた実装で、リンクリスト特有のメソッドがいくつか追加されています。
スタックやキューなどのデータ構造を表現したい場合は、LinkListを使用すると便利です。

addFirst(Object o) / addLast(Object o)

リストの先頭/末尾にオブジェクトを追加する

import java.util.LinkedList;

public class C3S1 {
  public static void main(String[] args) {
    LinkedList l = new LinkedList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.addFirst("*first*");
    l.addLast("*last*");
    System.out.println(l.toString());
  }
}
> java C3S1
[*first*, hoge, foo, bar, *last*]

getFirst() / getLast()

リストの先頭/末尾のオブジェクトを取得する。

import java.util.LinkedList;

public class C3S2 {
  public static void main(String[] args) {
    LinkedList l = new LinkedList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    System.out.println(l.getFirst());
    System.out.println(l.getLast());
  }
}
> java C3S2
hoge
bar

removeFirst() / removeLast()

リストの先頭/末尾のオブジェクトを削除し、削除したオブジェクトを取得する。

import java.util.LinkedList;

public class C3S3 {
  public static void main(String[] args) {
    LinkedList l = new LinkedList();
    l.add("hoge");
    l.add("foo");
    l.add("bar");
    l.add("hogehoge");
    l.add("foofoo");
    l.add("barbar");
    System.out.println("first = " + l.removeFirst());
    System.out.println("last = " + l.removeLast());
    System.out.println("rest = " + l.toString());
  }
}
> java C3S3
first = hoge
last = barbar
rest = [foo, bar, hogehoge, foofoo]

サンプル - Stackの実装

import java.util.LinkedList;

public class Stack {
  private final LinkedList list;

  public Stack() {
    list = new LinkedList();
  }

  public void push(Object o) {
    list.addFirst(o);
  }

  public Object pop() {
    return list.removeFirst();
  }
}
public class StackTest {
  public static void main(String[] args) {
    Stack stack = new Stack();
    stack.push("hoge");
    stack.push("foo");
    stack.push("bar");
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
  }
}
> java StackTest
bar
foo
hoge

Listと配列の相互変換

Listは配列に近いデータ構造であるため、これらを相互に変換したい場合があります。

配列からListへの変換

Listから配列に変換する場合は、java.util.ArraysのasList(Object[])メソッドを使用します。

import java.util.List;
import java.util.Arrays;

public class ArrayToList {
  public static void main(String[] args) {
    String[] array = {"hoge", "foo", "bar"};

    List list = Arrays.asList(array);

    System.out.println(list.toString());
  }
}

Listから配列への変換

配列からListに変換する場合は、java.util.ListのtoArray(Object[])メソッドを使用します。

import java.util.List;
import java.util.ArrayList;

public class ListToArray {
  public static void main(String[] args) {
    List list = new ArrayList();
    list.add("hoge");
    list.add("foo");
    list.add("bar");

    String[] array = (String[]) list.toArray(new String[list.size()]);

    for (int i = 0; i < array.length; i++) {
      System.out.println(array[i]);
    }
  }
}

Set

java.util.Setは集合を表現するデータ構造で、次のような実装クラスを持ちます。

その他にも J2SDK 1.4 からいくつかの実装クラスが追加されています。

Set特有のメソッド

java.util.Setはjava.util.Collectionをスーパーインタフェースに持ち、特別重要なメソッドは追加されていません。

各実装クラスの使い分け

実装クラス 使用する場面
java.util.HashSet 順序を考慮しなくてよい場合
java.util.TreeSet java.lang.Comparableを実装している、順序を持つオブジェクトを扱う場合

TreeSetの実装には2分探索木が使用されますので、データに順序関係のない場合は使用できませんし、データが元々整列している場合はパフォーマンスが低下します。

HashSet

この実装では各クラスのhashCode()メソッドを呼び出し、その値を用いてデータの格納位置を決定させるため、hashCode()メソッドが正しく実装されている必要があります。
また、順序は保証されません。

import java.util.Set;
import java.util.HashSet;

public class C4S1 {
  public static void main(String[] args) {
    Set s = new HashSet();
    s.add("hoge");
    s.add("foo");
    s.add("bar");
    s.add("moge");
    System.out.println(s.toString());
  }
}
> java C4S1
[foo, bar, hoge, moge]

TreeSet

この実装ではjava.lang.ComparableインターフェースのcompareTo(Object o)メソッドを呼び出し、その結果を用いてデータの格納位置を決定させるため、java.lang.Comparableインターフェースを実装していないオブジェクトを扱えません。
また、この集合はcompareToメソッドで定義された順序どおりに整列されます。

import java.util.Set;
import java.util.TreeSet;

public class C4S2 {
  public static void main(String[] args) {
    Set s = new TreeSet();
    s.add("hoge");
    s.add("foo");
    s.add("bar");
    s.add("moge");
    System.out.println(s.toString());
  }
}
> java C4S2
[bar, foo, hoge, moge]

この場合では、java.lang.Stringの定める順序 (アルファベット順) で整列しました。

Map

java.util.Mapはキーと値のペアを表現するデータ構造です。
MapはCollectionではありませんが、Collectionと深いかかわりがあります。
java.util.Map自体はインターフェースでインスタンス化できませんので、次のような実装クラスを使用します。

Mapが持つ機能

java.util.Collectionが持つメソッドのうち、特に使用されると思われるものを抜粋します。

Object put(Object key, Object value)

指定したキーと値のペアを追加する。

import java.util.Map;
import java.util.HashMap;

public class C5S1 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    System.out.println(m.toString());
  }
}
> java C5S1
{foo=456, bar=789, hoge=123}

get(Object key)

指定したキーに対応する値を取得する。
キーが登録されていない場合はnullを返す。

import java.util.Map;
import java.util.HashMap;

public class C5S2 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    System.out.println(m.get("hoge"));
    System.out.println(m.get("foo"));
    System.out.println(m.get("bar"));
    System.out.println(m.get("moge"));
  }
}
> java C5S2
123
456
789
null

containsKey(Object key)

指定したキーが存在するか検査する。

import java.util.Map;
import java.util.HashMap;

public class C5S3 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", null);
    System.out.println("hoge => " + m.get("hoge") + " : " + m.containsKey("hoge"));
    System.out.println("foo => " + m.get("foo") + " : " + m.containsKey("foo"));
    System.out.println("bar => " + m.get("bar") + " : " + m.containsKey("bar"));
  }
}
> java C5S3
hoge => 123 : true
foo => null : true
bar => null : false

remove(Object key)

指定したキーを持つエントリを削除する。

import java.util.Map;
import java.util.HashMap;

public class C5S4 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    m.remove("foo");
    System.out.println(m.toString());
  }
}
> java C5S4
{bar=789, hoge=123}

keySet()

キーの集合を取得する。

import java.util.Map;
import java.util.Set;
import java.util.HashMap;

public class C5S5 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    Set s = m.keySet();
    System.out.println(s.toString());
  }
}
> java C5S5
[foo, bar, hoge]

entrySet()

エントリ (キーと値のペア1つ) の集合を取得する。

import java.util.Map;
import java.util.Set;
import java.util.HashMap;

public class C5S6 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    Set s = m.entrySet();
    System.out.println(s.toString());
  }
}
> java C5S6
[foo=456, bar=789, hoge=123]

values()

値のグループを取得する。

import java.util.Map;
import java.util.Collection;
import java.util.HashMap;

public class C5S7 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    Collection c = m.values();
    System.out.println(c.toString());
  }
}
> java C5S7
[456, 789, 123]

boolean isEmpty()

マップが空かどうか検査する。

import java.util.Map;
import java.util.HashMap;

public class C5S8 {
  public static void main(String[] args) {
    Map m = new HashMap();
    System.out.println(m + " is Empty ? > " + m.isEmpty());
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    System.out.println(m + " is Empty ? > " + m.isEmpty());
  }
}
> java C5S8
{} is Empty ? > true
{foo=456, bar=789, hoge=123} is Empty ? > false

int size()

マップに格納されているエントリの個数を取得する。

import java.util.Map;
import java.util.HashMap;

public class C5S9 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    System.out.println(m.size());
  }
}
> java C5S9
3

HashMap

キーの検索にハッシュを使用するマップです。
この実装では各クラスのhashCode()メソッドを呼び出し、その値を用いてデータの格納位置を決定させるため、hashCode()メソッドが正しく実装されている必要があります。
また、順序は保証されません。

import java.util.Map;
import java.util.HashMap;

public class C6S1 {
  public static void main(String[] args) {
    Map m = new HashMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    m.put("moge", "abc");
    System.out.println(m.toString());
  }
}
> java C6S1
{foo=456, bar=789, hoge=123, moge=abc}

TreeMap

キーの検索に二分探索木を使用するマップです。
この実装ではjava.lang.ComparableインターフェースのcompareTo(Object o)メソッドを呼び出し、その結果を用いてデータの格納位置を決定させるため、java.lang.Comparableインターフェースを実装していないオブジェクトを扱えません。
また、この集合はcompareToメソッドで定義された順序どおりに整列されます。

import java.util.Map;
import java.util.TreeMap;

public class C6S2 {
  public static void main(String[] args) {
    Map m = new TreeMap();
    m.put("hoge", "123");
    m.put("foo", "456");
    m.put("bar", "789");
    m.put("moge", "abc");
    System.out.println(m.toString());
  }
}
> java C6S2
{bar=789, foo=456, hoge=123, moge=abc}