Java : List(リスト)の基本
List はコレクション・フレームワークの1つです。
複数の要素を配列のように管理します。
しかも、配列と違いサイズを気にする必要はありません。
本記事では、そんな 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 の詳細については 後述 します)
リストの生成
ArrayList を new してリストを生成します。
今回は、要素の型に String を使います。
final List<String> list = new ArrayList<>();
System.out.println(list); // []
生成されたリストに要素はまだありません。
リストに要素を追加
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" の順で要素(文字列)を追加しています。
インデックスを指定して要素を参照
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"
要素の更新
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]
要素の削除
List.remove メソッドで、指定したインデックスの要素を削除します。
削除した要素より後ろの要素は、1つ前へと詰められます。
System.out.println(list); // [XXX, YYY, ZZZ]
list.remove(0);
System.out.println(list); // [YYY, ZZZ]
ここでご紹介した以外にも、List には便利なメソッドがいろいろあります。
下記の API 使用例の記事もご参照ください。
Listの種類
List の代表的な実装には ArrayList と LinkedList があります。
それぞれのクラスの簡単な特徴のまとめです。
クラス | 末尾に追加 | インデックスによる参照 | インデックスによる挿入・削除 | 特徴 |
---|---|---|---|---|
ArrayList | ◎(非常に高速) | ◎(非常に高速) | ×(非常に低速) | LinkedListに比べて全体的にパフォーマンスが良いです。 よって、基本的にはArrayListを使うことをおすすめします。 |
LinkedList | ○(高速) | ×(非常に低速) | △(普通) | ArrayListに比べて全体的にパフォーマンスは悪いです。 ただし、インデックスによる要素の挿入・削除はArrayListより高速です。 要素の挿入・削除を多用する場面では、LinkeListを使うことを検討しましょう。 |
ArrayList
Listの基本的な実装です。
要素は、内部的には配列で管理されます。
ArrayList には容量(capacity)があり、最初はある程度余裕をもった容量で配列が作られます。
そして、リストに要素を追加していき、容量いっぱいになったら自動的に配列が拡張されます。
※リストの "サイズ" と "容量" は別物なのでご注意ください。
リストを使う側は容量は気にしなくても問題ありません。
ArrayList側がうまく管理してくれます。
基本的には配列なので、要素の末尾への追加やインデックスによる参照は非常に高速です。
反面、要素を削除すると後続の要素を1つ前にずらすという処理が入るため遅くなります。
LinkedList
LinkedList は要素を配列で管理しません。
要素と要素をリンクでつなぐことによって順序を管理します。
イメージ図になります。
例えば、LinkedList.get(2) で要素(c)を取得しようとすると、
- 先頭の要素(a)からリンク(次へ)をたどり、要素(b)へ。
- 要素(b)からリンク(次へ)をたどり、要素(c)を取得。
となります。
LinkedList.get(2)は先頭からのほうが近いですが、LinkedList.get(4)など後尾からリンク(前へ)でたどったほうが近い場合は、そちらからたどっていきます。
LinkedList.get(100000)だと、100000回リンクをたどることになります。
LinkedListのインデックス参照が非常に遅い理由がイメージできましたでしょうか。
リンクで要素にたどりつけさえすれば、そのインデックスへの要素の挿入や削除は高速です。
リンクを張り替えるだけで済むからです。
インデックス2の要素(c)を削除するイメージです。
ただし、リストの真ん中あたりの要素を削除するには、結局その要素までリンクをたどることになり、そこまで高速ではありません。
そのあたりはご注意ください。
補足
List vs. 配列
リストと配列は似たような用途で使われます。
それではリストと配列どちらを使った方がよいのでしょうか…?
おすすめはリストです。
- サイズの管理をしなくてい。
- 不変(イミュータブル) なリストも作れる。
- スレッドセーフなリストも作れる。
などなど、優れているところが多いです。
あえて配列を使いたいケースは、
- パフォーマンス最重視
- プリミティブ型( int や double など) を使いたい
という感じでしょうか。
配列については下記の記事もご参照ください。
Vectorについて
List の実装に Vectorクラスがあります。
ただし、Vector は古いAPIです。
もしスレッドセーフなリストが必要な場合は、Vector ではなく Collections.synchronizedList を使うことをおすすめします。
ArrayList でも LinkedList でもスレッドセーフにできるので、こちらを使っていきましょう。
他のコレクション
List 以外のコレクションには、Set や Map、Queue などがあります。
本記事で紹介した LinkedList は、実は Queue の実装でもあります。
もし興味がありましたら、関連記事やAPI仕様などをご確認ください。
関連記事
- 配列 (Array) の使い方
- 拡張for文 (for-eachループ文)
- Map(マップ)の基本
- Set(セット)の基本
- List の初期化方法いろいろ
- Map の初期化方法いろいろ
- Set の初期化方法いろいろ
- 配列 vs. List
- 配列からListへの変換
- List から配列への変換
- API 使用例
- Collection (コレクション)
- Collections (コレクション操作)
- Comparable
- Comparator
- Iterator
- List (リスト)
- Map (マップ)
- Map.Entry (キーと値のペア)
- Queue (キュー)
- AbstractQueue
- ArrayBlockingQueue (ブロッキング・配列キュー)
- ArrayDeque (両端キュー)
- BlockingDeque (ブロッキング・両端キュー)
- BlockingQueue (ブロッキング・キュー)
- ConcurrentLinkedDeque (並列処理用・両端キュー)
- ConcurrentLinkedQueue (並列処理用キュー)
- Deque (両端キュー)
- LinkedBlockingDeque (ブロッキング・リンク両端キュー)
- LinkedBlockingQueue (ブロッキング・リンクキュー)
- LinkedList (二重リンク・リスト)
- PriorityBlockingQueue (ブロッキング・優先度付きキュー)
- PriorityQueue (優先度付きキュー)
- RandomAccess
- Set (セット)
- Spliterator