S-JIS[2014-07-05/2023-09-24] 変更履歴
|
|
リストは、末尾に要素を追加してゆき、先頭から順番に全要素を処理するのに適したコレクション。
同じ値(要素)を複数保持することも出来る。
リストでは要素の並び順が保証される。
→Setは値(要素)は1種類ずつしか保持できず、並び順も(基本的には)不定 。その代わり、存在有無のチェック(containsメソッド)は得意。
リストはjava.util.Listインターフェースで表される。
具象クラスとしては、java.util.ArrayListが使われることが多い。
保持する要素の型(クラス)はジェネリクスで指定するので、以下のようにして使う。
import java.util.List; import java.util.ArrayList;
List<String> slist = new ArrayList<>(10); // 要素がStringのリスト List<Integer> ilist = new ArrayList<>(10); // 要素がIntegerのリスト
具象クラスの種類に関わらず、変数の型はListにすることが多い。
これは、その変数を「リストとして扱いたい」という意図であり、具象クラスの種類は(とりあえず)何でもいいから。
実際、Arrays.asList()の戻り値やUnmodifiableListを受け取ることもあるので、インターフェースで宣言しておく方が都合が良い。
Listの上にはCollectionというインターフェースもあるのだが、これはSetと共通なので、リストとして扱いたい場合はやはりListインターフェースを指定するのが妥当だろう。
ListはIterableインターフェースを継承しているので、for each構文で使うことが出来る。
→順次アクセス処理
Listの主な具象クラス。
クラス名 | JDK | 説明 | インスタンスを生成する例 | |
---|---|---|---|---|
java.util |
ArrayList |
1.2 | 内部では配列(Array)で要素を保持する。 リストだが、ランダムアクセスが得意。
ArrayListに要素を追加すると、内部の配列に保持される。 |
List<String> list = new ArrayList<String>(10); |
java.util |
LinkedList |
1.2 | 双方向リスト。 先頭と末尾の要素への追加・削除・取得が得意。ランダムアクセスは苦手。 ArrayListより若干メモリー消費量が多い。 (Java以外の言語で「リスト」と言ったら、大抵はLinkedListに似たものを指す。 しかしJavaの実装では、速度面でもメモリー使用量の面でもArrayListの方が優秀であり、LinkedListを使うことはまず無い。[/2020-05-29]) |
List<String> list = new LinkedList<String>(); |
java.util |
Vector |
1.0 | 古いライブラリー以外で使うことは無い。 位置づけとしてはsynchronizedされたArrayListだが、その目的ならCopyOnWriteArrayListの方が良い。 |
|
java.util.Arrays |
ArrayList |
1.5 | 配列を扱うArraysクラスに、配列からリストに変換するasListメソッド(可変長引数)がある。 このメソッドは初期値を指定したリストを作るのに使える。 ただし、このメソッドが返す具象クラスはArraysの内部クラスのArrayListである。(単純名はArrayListだが、java.util.ArrayListとは別もの) このArrayListは要素の追加・削除が出来ない(setによって変更することは出来る)ので注意が必要。 |
List<String> list = Arrays.asList("a", "b", "c"); |
java.util.Collections |
EmptyList |
1.2 | 空の不変リスト。 このEmptyListは要素の追加は出来ない。 要素の取得・削除も出来ない。(要素が無いので) |
List list = Collections.EMPTY_LIST; // JDK1.4 |
List<String> list = Collections.emptyList(); |
||||
java.util.Collections |
SingletonList |
1.2 | 要素が1つの不変リスト。 このSingletonListは要素の追加・削除・変更は出来ない。 |
List<String> list = Collections.singletonList("a"); |
java.util.Collections |
UnmodifiableList
|
1.2 | 不変リスト。 元となるリストを指定すると、値取得専用のリストになって返ってくる。 (元となったリストがRandomAccessかどうかは配慮される) このUnmodifiableListは要素の追加・削除・変更は出来ない。 元となったリストを変更すると、その内容は反映される。 |
List<String> list = 〜; |
java.util. |
AbstractImmutableList |
9 | 不変リスト。[2018-06-02] List.ofメソッドで返されるリスト。 |
List<String> list = Llist.of("a", "b"); |
java.util.Collections |
CheckedList |
1.5 | 動的型保証されるリスト。 不正な型の値(要素)を入れたらClassCastExceptionを発生させてくれる。 |
|
java.util.Collections |
SynchronizedList
|
1.2 | 同期化されるリスト。 元となるリストを指定すると、更新が同期化(synchronized)されるリストになる。 が、これを使うよりCopyOnWriteArrayListの方が良い。 |
|
java.util.concurrent |
CopyOnWriteArrayList |
1.5 | マルチスレッドセーフなリスト(ArrayList相当)。 →MTセーフなコレクション |
List<String> list = new CopyOnWriteArrayList<String>(); |
配列だと初期値を列挙する初期化構文がある(Scalaのリスト(Seq)にも初期値を指定する方法がある)が、JavaのListでは構文としては用意されていない。
初期値を設定したリストを作るには以下のような方法が考えられる。
方法 | JDK | 例 | 説明 |
---|---|---|---|
通常 | List<String> list = new ArrayList<>(10); |
普通にaddする方法。 | |
asList | 1.5 | List<String> list = Arrays.asList("a", "b", "c"); |
Arrays.asListメソッドは可変長引数なので、初期値を指定する感じで使える。 ただし、このリストは要素の追加・削除が出来ないので注意。 なお、要素を変更(setやsort)することは出来るので、不変リストというわけでもない。 |
addAll | 1.5 | List<String> list = new ArrayList<>(); |
Collections.addAllメソッドで複数の値を追加することが出来る。 ちなみにList#addAll()は他のコレクションの全要素を追加するメソッドであり、複数の値を追加するメソッドはListには無い。 |
初期化子 | List<String> list = new ArrayList<String>()
{{ add("a");add("b");add("c"); }}; |
インスタンス初期化子を使う方法。 とてもカッチョイイ書き方なのだが(笑)、この方法を知らない人が見たら非常に不可解に思えるので、実務的には使用すべきではないだろう^^; |
|
Stream | 1.8 | List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList()); |
Streamを使う方法。 ちょっと長めだが、Arrays.asListを使う方法とは異なり、java.util.ArrayListが返ってくる。 |
of | 9 | List<String> list = List.of("a", "b", "c"); |
List.ofメソッドは不変リストを返す。[2017-09-24] 要素にnullを指定するとNullPointerExceptionが発生する。 |
連番が欲しい場合 | 1.7以前 | List<Integer> list = new ArrayList<>(10); |
|
1.8 | List<Integer> list = IntStream.rangeClosed(1,
10).boxed().collect(Collectors.toList()); |
||
同じ値で埋める場合 | 1.7以前 | List<String> list = new ArrayList<>(4); |
Collections.fill()というメソッドもあるのだが、これは既に存在している部分を全て同じ値にするもの。空のリストには使えない。 |
1.8 | List<String> list = Stream.generate(()
-> "a").limit(4).collect(Collectors.toList()); |
||
1.2 | List<String> list = Collections.nCopies(4, "a"); |
Collections.nCopiesメソッドは不変リストを返す。[2018-01-21] | |
他のコレクション から作る場合 |
List<String> list2 = new ArrayList<>(list); |
||
10 | List<String> list2 =
List.copyOf(list); |
List.copyOfメソッドは不変リストを返す。[2018-06-02] |
“インデックスを使ったアクセスメソッドしか存在しないクラス”から、その一覧(リスト)を作成する例。
// Excelのシート一覧を作成する(POI) Workbook workbook = WorkbookFactory.create(new File(excelFile)); List<Sheet> sheets = IntStream.range(0, workbook.getNumberOfSheets()).mapToObj(i -> workbook.getSheetAt(i)).collect(Collectors.toList());
IntStream.rangeメソッドに開始インデックス(0)と終了インデックス(要素の個数)を指定し、インデックスのStreamを作成する。
後は、mapToObjメソッドで、インデックスを使ったアクセスメソッドを呼び出し、欲しいオブジェクトを取得する。
空のリストを使いたい場合は、Collections.emptyListを使うのが便利。[2010-05-29]
import java.util.Collections;
List<String> list = Collections.emptyList();
ただし、このemptyListは不変リストなので、要素を追加することは出来ない。
List関連の主なメソッド。
クラス | メソッド | JDK | 説明 | 例 | |
---|---|---|---|---|---|
コーディング | 結果 | ||||
java.util |
boolean add(E element) |
(末尾に)要素を追加する。 常にtrueが返る。(addメソッドはCollectionで定義されているメソッドなので、List以外ではfalseが返ることもある) |
List<String> list1 = new ArrayList<>(); |
[a, b, c] |
|
java.util |
boolean add(int index,
E element) |
指定された位置に要素を追加する。(それまで有った要素は後ろにずれる) |
List<String> list2 = new ArrayList<>(list1); |
[a, z, b, c] |
|
java.util |
void addFirst(E
element) |
21 | 先頭に要素を追加する。 |
List<String> list2 = new ArrayList<>(list1); |
[z, a, b, c] |
java.util |
void addLast(E
element) |
21 | 末尾に要素を追加する。 |
List<String> list2 = new ArrayList<>(list1); |
[a, b, c, z] |
java.util |
boolean addAll(Collection<E> c) |
渡されたコレクションの全要素を自分のList(の末尾)に追加する。 渡されたコレクションが空の場合はfalseが返る。 |
List<String> list2 = new ArrayList<>(list1); |
[a, b, c, x, y, z] |
|
java.util |
boolean addAll(int index,
Collection<E> c) |
渡されたコレクションの全要素を自分のListの指定された位置に追加する。 |
List<String> list2 = new ArrayList<>(list1); |
[a, x, y, z, b, c] |
|
java.util |
E set(int index,
E element) |
指定された位置に新しい値をセットする。 指定する位置は、要素が存在している範囲内である必要がある。位置が範囲外の場合はIndexOutOfBoundsExceptionが発生する。 セット前に保持していた要素(値)を返す。 |
List<String> list2 = new ArrayList<>(list1); |
[a, z, c] |
|
java.util |
E get(int index) |
指定された位置の要素(値)を返す。 位置が範囲外の場合はIndexOutOfBoundsExceptionが発生する。 →ランダムアクセス |
String s = list1.get(1); |
b |
|
java.util |
E getFirst() |
21 | 先頭の要素を取得する。 |
String s = list1.getFirst(); |
a |
java.util |
E getLast() |
21 | 末尾の要素を取得する。 |
String s = list1.getLast(); |
c |
java.util |
void forEach( (E)
-> void action) |
1.8 |
全要素を順次処理する。 引数として、処理を行う関数を渡す。 |
list1.forEach(element -> System.out.println(element)); |
a |
java.util |
Iterator<E> iterator() |
Iteratorを返す。 |
for (Iterator<String> i = list1.iterator(); i.hasNext();) { |
a |
|
java.util |
ListIterator<E> listIterator() |
ListIteratorを返す。 通常のIteratorは次の要素に移ることしか出来ないが、ListIteratorは前の要素に移動することが出来る。
nextIndex()やpreviousIndex()でインデックス(位置)を取得することも出来る。 |
ListIterator<String> i = list1.listIterator(); |
a |
|
java.util |
ListIterator<E> listIterator(int index) |
for (ListIterator<String> i = list1.listIterator(list1.size());
i.hasPrevious();) { |
c |
||
java.util |
Spliterator<E> spliterator() |
1.8 |
Streamに変換する際に使われる。 →stream() |
||
java.util |
int size() |
保持している要素の個数を返す。 LinkedListの場合でも内部で要素数を保持しているので、すぐに要素数が返る。 |
int size = list1.size(); |
3 |
|
java.util |
boolean isEmpty() |
空リスト(要素を保持していない)の場合、trueを返す。 |
boolean empty = list1.isEmpty(); |
false |
|
java.util |
int indexOf(Object
element) |
要素の位置を返す。(先頭から順番に探索する) 保持していなかった場合は-1が返る。 |
int n = list1.indexOf("b"); |
1 |
|
java.util |
int lastIndexOf(Object element) |
要素の位置を返す。(末尾から順番に探索する) 保持していなかった場合は-1が返る。 |
int n = list1.lastIndexOf("b"); |
1 |
|
java.util |
boolean contains(Object element) |
指定された要素を保持している場合、trueを返す。 線形探索になるので、保持している要素数が非常に多いと遅くなるので注意。 |
boolean exists = list1.contains("b"); |
true |
|
java.util |
boolean containsAll(Collection<?> c) |
渡されたコレクションに入っている全要素が自分のList内に存在していたらtrueを返す。 |
Set<String> set = new HashSet<>(); |
true |
|
java.util |
void clear() |
全要素を削除する。 |
list2.clear(); |
[] |
|
java.util |
boolean remove(Object element) |
要素を削除する。 実際に削除した(要素を保持していた)場合はtrueが返る。 |
List<String> list2 = new ArrayList<>(list1); |
[a, c] |
|
java.util |
E remove(int index) |
指定された位置にある要素を削除する。 位置が範囲外の場合はIndexOutOfBoundsExceptionが発生する。 削除された要素(値)を返す。 |
List<String> list2 = new ArrayList<>(list1); |
[a, c] |
|
java.util |
E removeFirst() |
21 | 先頭の要素を削除する。 |
List<String> list2 = new ArrayList<>(list1); |
[b, c] |
java.util |
E removeLast() |
21 | 末尾の要素を削除する。 |
List<String> list2 = new ArrayList<>(list1); |
[a, b] |
java.util |
boolean removeAll(Collection<?> c) |
自分のList内の各要素に対し、渡されたコレクションにその要素が含まれていたら、自分のListからその要素を削除する。 1つでも削除した場合はtrueが返る。 |
Set<String> set = new HashSet<>(); |
[a] |
|
java.util |
boolean removeIf( (E) -> boolean filter) |
1.8 |
条件に合致する要素を削除する。 引数として、条件判定用の関数を渡す。 →Stream#filter()(Stream#filter()は、条件に一致するものを抽出する(残す)) |
List<String> list2 = new ArrayList<>(list1); |
[a, c] |
java.util |
boolean retainAll(Collection<?> c) |
自分のList内の各要素に対し、渡されたコレクションにその要素が含まれていたら、その要素だけを残す(他の要素を削除する)。 1つでも削除した場合はtrueが返る。 |
Set<String> set = new HashSet<>(); |
[b, c] |
|
java.util |
void replaceAll( (E) -> E operator) |
1.8 |
各要素を別の値(要素の型は同じ)に変換する。 引数として、変換用の関数を渡す。 →Stream#map() |
List<String> list2 = new ArrayList<>(list1); |
[A, B, C] |
java.util |
void sort( Comparator <E> c) |
1.8 |
自分のList内の要素をソートする。 Listをソートするには、JDK1.7以前はCollections.sort()を使っていたが、1.8ではList#sort()の方が具象クラス毎に実装されていて効率が良い。 |
List<String> list2 = new ArrayList<>(list1); |
[c, b, a] |
java.util |
SequencedCollection<E>
reversed(E element) |
21 | 要素を逆順にしたListを返す。 |
List<String> list2 = list1.reversed(); |
[c, b, a] |
java.util |
List<E> subList(int fromIndex,
int toIndex) |
指定された範囲のリストを返す。 このリストは元のリストの一部分であり、変更は共有される。 |
List<String> list2 = new ArrayList<>(list1); |
[a, b, c] |
|
java.util |
Object[] toArray() |
保持している全要素をObject配列にして返す。 |
Object[] array = list1.toArray(); |
[a, b, c] |
|
java.util |
T[] toArray(T[] array) |
保持している全要素を配列に入れて返す。 渡された配列に全要素が収まれば、その配列が返る。 渡された配列のサイズが小さい場合は拡張された配列が返る。 |
String[] array = list1.toArray(new String[list1.size()]); |
[a, b, c] |
|
java.util |
T[] toArray(IntFunction<T[]>) |
11 | 保持している全要素を配列に入れて返す。[2018-10-01] |
String[] array = list1.toArray(String[]::new); |
[a, b, c] |
java.util |
Stream<E> stream() |
1.8 | Stream(直列ストリーム)を返す。 |
Stream<String> stream = list1.stream(); |
|
java.util |
Stream<E> parallelStream() |
1.8 | Stream(並列ストリーム)を返す。 |
Stream<String> stream = list1.parallelStream(); |
クラスやメソッドによっては、java.lang.UnsupportedOperationExceptionを返す実装もある。
例えばUnmodifiableListは不変リストなので、値を変更するaddやsetメソッドを呼び出すとUnsupportedOperationExceptionが発生する。
リストでは、保持している値(要素)を先頭から順番に処理していくことがある。
Iteratorパターン | JDK1.4 |
for (Iterator i = list.iterator(); i.hasNext();) { 型 変数 = (型) i.next(); 〜 } |
|
---|---|---|---|
JDK1.5 |
for (Iterator<型> i = list.iterator(); i.hasNext();) { 型 変数 = i.next(); 〜 } |
JDK1.5ではジェネリクスが使えるようになった。 | |
for each構文 | JDK1.5 |
for (型 変数 : list) { 〜 } |
ListはIterableを継承しているので、for each構文を使うことが出来る。 |
forEachメソッド | JDK1.8 |
list.forEach(変数 -> { 〜 }); |
forEachメソッドを使い、ラムダ式で処理を記述する方法。 |
RandomAccessインターフェースを実装しているリストであっても、for each構文の場合は(内部では)Iteratorパターンになってしまうが、
forEachメソッドは具象クラスごとに効率の良い実装になっているので、forEachメソッドを使う方が良さそう。
リストの全要素を別の型に変換して新しいリストを作成する例。
JDK1.4 |
List slist = Arrays.asList(new Object[]{ "a.txt", "b.txt", "c.txt"} ); List flist = new ArrayList(slist.size()); for (Iterator i = slist.iterator(); i.hasNext();) { String s = (String) i.next(); flist.add(new File(s)); } |
元のListの要素に順次アクセスして、新しいListにaddしていく。 |
---|---|---|
JDK1.5 |
List<String> slist = Arrays.asList("a.txt", "b.txt", "c.txt"); List<File> flist = new ArrayList<File>(slist.size()); for (String s : slist) { flist.add(new File(s)); } |
|
JDK1.8 |
List<String> slist = Arrays.asList("a.txt", "b.txt", "c.txt"); List<File> flist = slist.stream().map(s -> new File(s)).collect(Collectors.toList()); |
Streamのmapメソッドを使うと、一行で済む。 |
特定条件の要素を削除する例。
JDK1.5 |
List<String> slist = new ArrayList<String>(Arrays.asList("a", "bb", "ccc")); for (Iterator<String> i = slist.iterator(); i.hasNext();) { String s = i.next(); if (s.length() == 2) { i.remove(); } } |
Iteratorパターン。 |
---|---|---|
JDK1.8 |
List<String> slist = new ArrayList<>(Arrays.asList("a", "bb", "ccc")); slist.removeIf(s -> s.length() == 2); |
リスト内の重複した要素を1つにまとめる例。
JDK1.5 |
public static List<String> distinct(List<String> slist) { List<String> dlist = new ArrayList<String>(); Set<String> set = new HashSet<String>(); for (String s : slist) { if (!set.contains(s)) { set.add(s); dlist.add(s); } } return dlist; } |
別のリストを用意して、重複しない要素だけ追加していく方法。 |
---|---|---|
JDK1.5 |
public static void distinct(List<String> slist) { Set<String> set = new HashSet<String>(); for (Iterator<String> i = slist.iterator(); i.hasNext();) { String s = i.next(); if (set.contains(s)) { i.remove(); } else { set.add(s); } } } |
Iteratorパターンで、重複する要素だけ削除していく方法。 (この方法では、元のリストを変更する) |
JDK1.5 |
public static List<String> distinct(List<String> slist) { return new ArrayList<String>(new LinkedHashSet<String>(slist)); } public static Collection<String> distinct(List<String> slist) { return new LinkedHashSet<String>(slist); } |
LinkedHashSetを使う方法。 LinkedHashSetは重複を排除してくれるし、並び順も保証される。 |
JDK1.8 |
public static List<String> distinct(List<String> slist) { return slist.stream().distinct().collect(Collectors.toList()); } |
Streamのdistinctメソッドを使う方法。 |
ArrayList(やVector)は、RandomAccessインターフェースを実装している。[2007-03-26]
これは、「順次アクセス操作(Iteratorを使って先頭から順番にアクセスする)よりも、ランダムアクセス操作(インデックスを使って途中の要素を直接指定する)の方が高速(であると想定される)」ということを意味する。
(RandomAccessを実装していないリストの代表は、LinkedList)
リストの本来の姿では、全要素を順番に操作するにはIteratorパターンを使う。
しかしRandomAccessインターフェースを実装しているコレクションでは、インデックスで直接指定する方が実行速度が速い。
と言ってもそんなに大差ない(Iteratorインスタンスの生成分と、ループ1回につきメソッド呼び出しが1〜2回分増える程度)。
JDK1.4 | JDK1.5以降 |
---|---|
for (int i = 0; i < list.size(); i++) { 型 要素 = (型) list.get(i); 〜 } |
for (int i = 0; i < list.size(); i++) { 型 要素 = list.get(i); 〜 } |
(ArrayListの場合、インデックスを使う方がIteratorパターンより3倍くらい速い)
→ArrayListとLinkedListの順次アクセス時間
ただしfor each構文ではRandomAccessまでは判断してくれず、ArrayListであってもIteratorで展開されてしまう。[2007-06-11]
forEachメソッドは(RandomAccessインターフェースとは無関係に)具象クラスごとに個別の実装になっている。[2014-07-05]
リストから配列を生成するにはtoArrayメソッドを使用する。[2007-12-07]
List<String> list = new ArrayList<String>(); list.add("abc"); list.add("def"); String[] array = list.toArray(new String[list.size()]);
Java9より前では、配列からリスト(サイズ不変ArrayList)を生成するにはArrays.asListメソッドを使うのが便利。
String[] array = { "abc", "def" }; List<String> list = Arrays.asList(array);
asListメソッドはJDK1.5で可変長引数に置き換えられたので、以下のような書き方も可。
List<String> list = Arrays.asList("abc", "def");
ただし、asList()が返すのはサイズが変えられないリストなので注意。[2008-08-11/2010-03-06]
(つまり、add()したりremove()したり出来ない。set()すると元の配列の値も書き換わる。要するにこのリストは配列のラッパー)
Java9以降では、Arrays.asListよりList.ofの方が良いと思う。[2010-05-29]
(Listを取得するならListのクラスのメソッドを使うべき。また、List.ofの戻り値は不変リストである)
List<String> list = List.of("abc", "def");
リストと配列は、どちらも値(要素)を順番に保持することが出来るので、似ている。
では、メソッドの引数や戻り値の型としてリストと配列のどちらを使うべきか?というと、今はリストを使えばいいと思う。
JDK1.4以前ではジェネリクスが無かった為、リストの要素の種類(型)は何だか分からなかった。
その点、配列は要素の種類(型)が明示されるので、ソースを見たときにすぐ分かるし、異なる型を扱おうとしたらコンパイルエラーになるし、配列を使う利点があった。
JDK1.5以降ではリストに型引数(要素の種類(型))が指定できるようになったので、配列だけに有利な点は無くなった。
むしろ、全般的にリストの方が有利である。
(配列を使う必要があるのは、可変長引数くらいなのではなかろうか)
リスト | 配列 | 備考 | |
---|---|---|---|
戻り型に指定し、 変更を許す場合 |
public List<String> getResults() { List<String> result = new ArrayList<>(); result.add("a"); result.add("b"); return result; } |
public String[] getResults() { String[] result = { "a", "b" }; return result; } |
戻ってきたものが配列だと、変更がしづらい。 |
List<String> result = getResults(); result.add("z"); |
String[] result0 = getResults(); String[] result = Arrays.copyOf(result0, result0.length + 1); result[result0.length] = "z"; |
||
戻り型に指定し、 変更を許さない場合 |
private static final List<String> RESULTS; static { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); RESULTS = Collections.unmodifiableList(list); } public List<String> getResults() { return RESULTS; } |
private static final String[] RESULTS = { "a", "b" }; public String[] getResults() { return Arrays.copyOf(RESULTS, RESULTS.length); } |
配列では、変更不可にする方法は無い。 |
リストからStreamを生成するにはstreamもしくはparallelStreamメソッドを使用する。
List<String> list = new ArrayList<>(); list.add("abc"); list.add("def"); Stream<String> stream = list.stream();
Streamからリストを生成するにはCollectors.toListを使用する。
Stream<String> stream = Stream.of("abc", "def"); List<String> list = stream.collect(Collectors.toList());
Collectors.toListの内部は、java.util.ArrayListを作成して、Stream内の要素をaddしていく形になっている。