Java : List(リスト)の基本

List はコレクション・フレームワークの1つです。
複数の要素を配列のように管理します。
しかも、配列と違いサイズを気にする必要はありません。

本記事では、そんな List の基本的な使い方を解説していきます。


Listの概要

Listクラス図

順序付けられたコレクションです。シーケンスとも呼ばれます。
このインタフェースのユーザーは、リスト内のどこに各要素が挿入されるかを精密に制御できます。
ユーザーは整数値のインデックス(リスト内の位置)によって要素にアクセスしたり、リスト内の要素を検索したりできます。

List は順序付けされたコレクションです。
要素を追加するたびに 0, 1, 2, 3 ... とインデックスが割り振られて、厳密に順序が管理されます。

配列をイメージしていただくと分かりやすいかもしれません。

ただし、配列は生成するときに サイズを指定 する必要があります。
一方、リストはサイズの指定が不要です。必要に応じて 自動で拡張 されていきます。

サイズを管理しなくてよい、というのが配列とリストの大きな違いであり、リストのメリットでもあります。

リストの例

// 生成時にサイズを指定する必要がありません。
final List<String> list = new ArrayList<>();

System.out.println(list); // []
System.out.println(list.size()); // 0

list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");

// サイズは自動で拡張されます。
System.out.println(list); // [aaa, bbb, ccc, ddd]
System.out.println(list.size()); // 4

配列の例

// 生成時にサイズを指定する必要があります。
final String[] array = new String[3];

System.out.println(Arrays.toString(array)); // [null, null, null]
System.out.println(array.length); // 3

array[0] = "aaa";
array[1] = "bbb";
array[2] = "ccc";

System.out.println(Arrays.toString(array)); // [aaa, bbb, ccc]
System.out.println(array.length); // 3

// 配列のサイズが3なので、4つめの要素を書き込めません。
// 書き込もうとすると、非チェック例外の ArrayIndexOutOfBoundsException が発生します。
array[3] = "ddd"; // ArrayIndexOutOfBoundsException 発生

サイズを管理しなくてよい、というのは重要です。
プログラマが考えなければならないことが1つ減るからです。
コードもシンプルになり、結果としてプログラムの品質も上がることが期待できます。

使い方の基本

それでは、Listの使い方をコード例で見ていきましょう。
List には ArrayList を使います。(ArrayList の詳細については 後述 します)

リストの生成

public ArrayList()
初期容量10で空のリストを作成します。

ArrayList を new してリストを生成します。
今回は、要素の型に String を使います。

final List<String> list = new ArrayList<>();

System.out.println(list); // []

生成されたリストに要素はまだありません。

リストに要素を追加

boolean add(E e)
指定された要素をこのリストの最後に追加します(オプションの操作)。

List.add メソッドで、リストの末尾に要素を追加できます。

list.add("aaa");
System.out.println(list); // [aaa]

list.add("bbb");
System.out.println(list); // [aaa, bbb]

list.add("ccc");
System.out.println(list); // [aaa, bbb, ccc]

コード例では、"aaa", "bbb", "ccc" の順で要素(文字列)を追加しています。

インデックスを指定して要素を参照

E get(int index)
このリスト内の指定された位置にある要素を返します。

List.get メソッドで、配列と同じようにインデックスで要素を参照できます。

System.out.println(list); // [aaa, bbb, ccc]

System.out.println(list.get(0)); // "aaa"
System.out.println(list.get(1)); // "bbb"
System.out.println(list.get(2)); // "ccc"

指定したインデックスが範囲外だと、非チェック例外の IndexOutOfBoundsException が発生します。

// IndexOutOfBoundsException 発生
list.get(3);

もし、すべての要素を先頭から順に参照したい場合は、拡張for文 が便利です。

for (final var item : list) {
    System.out.println(item);
}

// 結果
// ↓
// "aaa"
// "bbb"
// "ccc"

上記のコードは、次の基本for文と同じに意味になります。

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 結果
// ↓
// "aaa"
// "bbb"
// "ccc"

要素の更新

E set(int index, E element)
このリスト内の指定された位置にある要素を、指定された要素に置き換えます(オプションの操作)。

List.set メソッドで、指定したインデックスの要素を上書きします。

System.out.println(list); // [aaa, bbb, ccc]

list.set(0, "XXX");
System.out.println(list); // [XXX, bbb, ccc]

list.set(1, "YYY");
System.out.println(list); // [XXX, YYY, ccc]

list.set(2, "ZZZ");
System.out.println(list); // [XXX, YYY, ZZZ]

要素の削除

E remove(int index)
このリスト内の指定された位置にある要素を削除します(オプションの操作)。 後続の要素を左に移動します(インデックスから1を減算)。

List.remove メソッドで、指定したインデックスの要素を削除します。
削除した要素より後ろの要素は、1つ前へと詰められます。

System.out.println(list); // [XXX, YYY, ZZZ]

list.remove(0);
System.out.println(list); // [YYY, ZZZ]

ここでご紹介した以外にも、List には便利なメソッドがいろいろあります。
下記の API 使用例の記事もご参照ください。

Listの種類

Listクラス図

List の代表的な実装には ArrayListLinkedList があります。
それぞれのクラスの簡単な特徴のまとめです。

クラス 末尾に追加 インデックスによる参照 インデックスによる挿入・削除 特徴
ArrayList ◎(非常に高速) ◎(非常に高速) ×(非常に低速) LinkedListに比べて全体的にパフォーマンスが良いです。
よって、基本的にはArrayListを使うことをおすすめします。
LinkedList ○(高速) ×(非常に低速) △(普通) ArrayListに比べて全体的にパフォーマンスは悪いです。
ただし、インデックスによる要素の挿入・削除はArrayListより高速です。

要素の挿入・削除を多用する場面では、LinkeListを使うことを検討しましょう。

ArrayList

Listインタフェースのサイズ変更可能な配列の実装です。

Listの基本的な実装です。
要素は、内部的には配列で管理されます。

ArrayList には容量(capacity)があり、最初はある程度余裕をもった容量で配列が作られます。
そして、リストに要素を追加していき、容量いっぱいになったら自動的に配列が拡張されます。

※リストの "サイズ" と "容量" は別物なのでご注意ください。

ArrayListイメージ

リストを使う側は容量は気にしなくても問題ありません。
ArrayList側がうまく管理してくれます。

基本的には配列なので、要素の末尾への追加やインデックスによる参照は非常に高速です。

反面、要素を削除すると後続の要素を1つ前にずらすという処理が入るため遅くなります。

ArrayListイメージ

LinkedList

ListおよびDequeインタフェースの二重リンク・リスト実装です。

LinkedList は要素を配列で管理しません。
要素と要素をリンクでつなぐことによって順序を管理します。

LinkedListイメージ

イメージ図になります。
例えば、LinkedList.get(2) で要素(c)を取得しようとすると、

  1. 先頭の要素(a)からリンク(次へ)をたどり、要素(b)へ。
  2. 要素(b)からリンク(次へ)をたどり、要素(c)を取得。

となります。

LinkedListイメージ

LinkedList.get(2)は先頭からのほうが近いですが、LinkedList.get(4)など後尾からリンク(前へ)でたどったほうが近い場合は、そちらからたどっていきます。

LinkedList.get(100000)だと、100000回リンクをたどることになります。
LinkedListのインデックス参照が非常に遅い理由がイメージできましたでしょうか。

リンクで要素にたどりつけさえすれば、そのインデックスへの要素の挿入や削除は高速です。
リンクを張り替えるだけで済むからです。

インデックス2の要素(c)を削除するイメージです。

LinkedListイメージ

ただし、リストの真ん中あたりの要素を削除するには、結局その要素までリンクをたどることになり、そこまで高速ではありません。
そのあたりはご注意ください。

補足

List vs. 配列

リストと配列は似たような用途で使われます。
それではリストと配列どちらを使った方がよいのでしょうか…?

おすすめはリストです。

  • サイズの管理をしなくてい。
  • 不変(イミュータブル) なリストも作れる。
  • スレッドセーフなリストも作れる。

などなど、優れているところが多いです。

あえて配列を使いたいケースは、

  • パフォーマンス最重視
  • プリミティブ型( intdouble など) を使いたい

という感じでしょうか。

配列については下記の記事もご参照ください。

Vectorについて

このクラスは、Java 2プラットフォームv1.2の時点でListインタフェースを実装するように改良された結果、Java Collections Frameworkのメンバーとなりました。
新しいコレクションの実装とは異なり、Vectorは同期をとります。
スレッドセーフな実装が必要ない場合は、Vectorの代わりにArrayListを使用することをお薦めします。

List の実装に Vectorクラスがあります。
ただし、Vector は古いAPIです。

もしスレッドセーフなリストが必要な場合は、Vector ではなく Collections.synchronizedList を使うことをおすすめします。

public static List synchronizedList​(List list)
指定されたリストに連動する同期(スレッドセーフな)リストを返します。

ArrayList でも LinkedList でもスレッドセーフにできるので、こちらを使っていきましょう。

他のコレクション

List 以外のコレクションには、SetMapQueue などがあります。
本記事で紹介した LinkedList は、実は Queue の実装でもあります。

もし興味がありましたら、関連記事やAPI仕様などをご確認ください。


関連記事

ページの先頭へ