JavaにはListというクラスがあります。配列と似ていると思われがちですが、配列とリストには大きな違いがあります。配列とリストの違いを解説しながらリストの理解を深め、JavaでListを使うための基本を習得しましょう。
- Java言語のリストとは
- リストインターフェースとは
- List と ArrayList の違い
- ArrayList と LinkedList の違い
- CopyOnWriteArrayList というクラスもある
- リストと配列の違い
- リストで使える基本的なメソッド
- リストの概要と基本的な使い方のまとめ
Java言語のリストとは
Java言語のリストとは、複数の値やオブジェクトを1つの変数に順番に格納していくためのクラスやインターフェースの総称です。
Listの実装にはArrayListやLinkedList、CopyOnWriteArrayListなどいくつかのクラスが定義されています。これらがリストだということを表すListインターフェースも用意されています。
リストインターフェースとは
Javaにはインタフェースという型があり、メソッドや定数などの一覧が定義できます。
リストインタフェースには、リストがどのようなメソッドを持つかが定義されています。例えばリストに要素を追加する “add” や、リストから要素を取得する “get”などが定義されており、リストインターフェースを見れば、リストがどのようなもので、どのように操作できるかがわかります。
リストインターフェースの定義
リストインターフェースに何が定義されているかは、Javaを開発しているOracleのウェブサイトでJavaDocを見るのが近道です。JavaDocはJavaのクラスやインターフェースの公式な仕様書です。
List と ArrayList の違い
Listはリストのインターフェースです。ArrayListはリストインターフェースに定義されたメソッドを実装した実装クラスです。ListとArrayList、両者はJava言語仕様上で根本的な違いがあります。
Listはインタフェース
ListはJavaのインタフェースです。リストインタフェースはメソッドなどの型を定義しているだけで、メソッド自体は実装されていません。リストが持つ機能の一覧表と考えて問題ありません。
ArrayListは実装クラス
ArrayListはリストインタフェースを実装した実装クラスです。Listの実装クラスは、リストインターフェースに定義された全てのメソッドを実装しています。
ArrayListの特徴
ArrayList は Listインタフェースを実装しており、Listインタフェースに定義されているメソッドは全て利用することができます。
ArrayListは、Listに追加された値にインデックスを付けて格納していきます。「3番目の値を参照したい」という要求には素早く対応できますが、要素の追加削除時にはインデックスの付け直しが発生します。
ArrayList と LinkedList の違い
ArrayList の他にも List を実装したクラスに LinkedList などがあります。
List の実装には ArrayList や LinkedList などがある
LinkedList も ArrayList と同じ List の実装クラスなので、両方ともListに定義されているメソッドを全て実装しています。具体的な実装方法は違いますが、Listとして同じ動作をします。
ArrayList と LinkedList はお互いに一長一短
ArrayList と LinkedList が実装するメソッドは同じでも、ArrayList と LinkedList とでは実装方法のコンセプトが異なります。実装方法の違いから、ArrayList は LinkedList と比べて、データの読み出しが得意ですが追加削除が苦手という特徴があります。
CopyOnWriteArrayList というクラスもある
Listの実装には、ArrayListやLinkedListの他にも CopyOnWriteArrayList というクラスもあります。
CopyOnWriteArrayListは、ArrayListをスレッドセーフにした実装です。ArrayListと比べて処理効率は非常に悪いものの、ArrayListをどうしてもスレッドセーフで使いたい場合は役に立つクラスです。
リストと配列の違い
Javaのリストと似たデータ形式に「配列」があります。
配列は、リストと同じように複数の値を格納できる変数の型ですが、概念や使い方が大きく違います。例えば、リストは自由に要素を追加削除できますが、配列は要素の追加や削除はできません。
配列とリストは相互変換できるので、配列で要素を追加削除したい場合は、一度リストへ変換して要素の追加削除を行った後、再度配列へ変換する必要があります。
オブジェクト生成方法の違い
リストと配列とでは、オブジェクトの生成方法から異なります。
リストと配列とではオブジェクトを生成する構文自体から違いはありますが、最も把握しておきたいのは、オブジェクト生成時の「要素数」や「初期値」の考え方です。
リストオブジェクトの生成方法
リストオブジェクトの生成には、new演算子を使用します。
例えばArrayListからオブジェクトを生成するコードは以下です。
ArrayList lst = new ArrayList();
ArrayListはListインターフェースの実装なので、以下とも書けます。
List lst = new ArrayList();
今回割愛しましたが、JavaのGenericsを用いてリスト値のデータ型を指定するのが一般的です。
配列オブジェクトの生成方法
配列オブジェクトの生成にもnew演算子を使用しますが、クラスよりは変数に近い書き方になります。例えば、要素数だけ3個と決めて値が空の配列を生成するコードは以下です。int[] arr = new int[3];
配列の場合、要素の値が決まっていれば以下のように初期値を設定することもできます。初期値を設定して配列オブジェクトを生成する場合、new演算子は不要です。int[] arr = {3, 7, 13};
要素の取得や検索と置き換え方法の違い
リストや配列には複数のデータを格納して利用するため、格納された複数のデータを効率的に利用する機能が備わっています。リストや配列に格納されたデータは、要素番号を指定してピンポイントで取り出したり値を置き換えたりすることができます。
オブジェクトの生成方法と同じく、データの取得や検索、置き換えの方法も、リストと配列で違いがあります。
要素の取得方法
リストも配列も、0から始まるインデックス番号を指定して値の取得ができます。インデックスを指定することは同じですが、リストと配列とでは実装方法が異なります。
リストから要素を取得するには、getメソッドを使用します。getメソッドの引数にインデックス番号を渡すと、そのインデックスに格納された要素の値が戻値として返ります。
配列から要素を取得するには、配列変更の後ろに「[インデックス番号]」と指定します。
要素の検索方法
リストに格納された値を検索するには、ListクラスのindexOfメソッドを使用します。indexOfメソッドの引数に検索したい値を指定します。指定した値がリスト内に存在するとそのインデックス番号が、存在しなければ-1の数値が返ってきます。
配列には要素を検索する機能はありません。配列自体に検索機能はありませんが、配列からListに変換した後にListクラスのメソッドを使って検索できます。
要素値の置き換え方法
リストも配列も、0から始まるインデックス番号を指定した要素値の置き換えができますが、リストと配列とでは要素値置き換えの実装方法が異なります。
リストの要素を置換する場合、Listクラスのsetメソッドを使用します。setメソッドに、変更したい要素のインデックス番号と置換後の値を指定します。
配列の要素を置換する場合、配列変更の後ろに「[インデックス番号]」を指定し、置換後の値を代入します。
要素の追加と削除方法の違い
リストは要素の追加削除が自由に行えますが、配列では要素の追加削除はできません。配列で要素の追加削除を行いたい場合は、いったん配列からリストへ変換する必要があります。
配列からリストへ変換した後は、どちらも同じリストですので、要素の追加削除方法は同じです。
要素の追加方法
リストにデータを追加する場合はListクラスのaddメソッドかaddAllメソッドを使います。
引数に追加したいデータを指定すれば、リストの末尾に要素が追加されます。リストの途中にデータを追加したい場合は、引数に追加したいインデックス番号と追加したいデータを指定します。
要素の削除方法
リストからデータを削除したい場合はListクラスのremoveメソッドかremoveAllメソッドを使います。removeメソッドを使えば、要素の置き換えと同じようにインデックスを指定して要素の削除が行えます。
removeとremoveAllを使えば、引数で指定したデータと一致する値を持つ要素を削除することもできます。
リストで使える基本的なメソッド
ここまでで、Javaの配列とリストについて、両者の違いを解説しながらリストが持つ主要なメソッドについて触れてきました。
ここから、内容が重複するものもありますが、JavaのListで使える基本的なメソッドについて、整理を兼ねてサンプルコードを交えながら具体的に解説していきます。
要素を追加するaddメソッド
リストに要素を1つ追加するにはaddメソッドを使用します。addメソッドには引数の異なる2種類あります。
boolean add(E e)
void add(int index, E element)
E はListインスタンス生成時にGenericsで指定した、リストに格納するデータの型です。
引数が1つのメソッドは、リストの末尾に引数で指定した値を追加します。引数が2つのメソッドは指定した位置にデータを追加します。
要素をまとめて追加するaddAllメソッド
リストに複数の要素をまとめて追加するにはaddAllメソッドを使用します。addAllにもaddと同じく2種類のメソッドがあり、使い方は同じです。
boolean addAll(Collection c)
boolean addAll(int index, Collection c)
引数のCollectionとはJavaのインタフェースで、実装にはArrayListなどのリストやHashSetなどがあります。
要素を取得するgetメソッド
リストから要素を取得すにはgetメソッドを使用します。getメソッドの引数には取得したい要素のインデックス番号を指定します。リスト先頭のインデックス番号は0なので注意しましょう。
T get(int index)
E はListインスタンス生成時にGenericsで指定した、リストに格納するデータの型です。
要素を先頭から順番に取得する方法
getメソッドを使えばリストから指定したインデックスの要素を取得できますが、リストの先頭から順番に値を取得したい場合、JavaのIteratorインタフェースや拡張for文も利用できます。
以下は Java 5 からサポートされた拡張for文の使用例です。
List data = new ArrayList();
for (Object element : data) {
任意の処理
}
先頭から末尾までの要素が順番に取得できます。
要素の置き換えをするsetメソッド
setメソッドを使うと、指定したインデックス番号にある要素の値を置き換えることができます。
E set(int index, E element)
E はListインスタンス生成時にGenericsで指定した、リストに格納するデータの型です。引数の型も戻り値の型もEですが、引数のEには置換したい値を指定し、戻り値には置換前の値が戻されます。
要素を削除するremoveメソッド
Listから要素を削除する場合はremoveメソッドを使用します。removeメソッドには、インデックスを指定するメソッドと、引数に指定したデータが格納された要素を削除するメソッドの2種類があります。
E remove(int index)
boolean remove(Object o)
戻り値は、removeにインデックスを指定すると削除前の値が返ります。データを指定すると削除対象が存在したかが返ります。
要素をまとめて削除するremoveAllメソッド
removeメソッドにデータを指定した場合、最初に見つかった1件のみが削除されます。複数のデータをまとめて削除したい場合はremoveAllメソッドを使用します。引数で指定したデータが1件でも存在した場合trueが返ります。
boolean removeAll(Collection c)
引数のCollectionとはJavaのインタフェースで、実装にはArrayListやHashSetなどがあります。
要素を検索して最初の位置を返すindexOfメソッド
あるデータがリストの何番目に格納されているかを調べたい場合は、indexOfメソッドを使用します。
int indexOf(Object o)
引数で指定したデータに合致する要素が1件以上存在した場合、最初に見つかったインデックス番号を返します。1件も存在しなかった場合は -1 を返します。
要素を検索して最後の位置を返すlastIndexOfメソッド
lastIndexOfメソッドを使えば、indexOfと同じ要素検索を行うことができ、最初ではなく最後のインデックス番号を取得することができます。
int lastIndexOf(Object o)
最後のインデックス番号を取得したい場合は、lastIndexOfメソッドを使用します。戻り値の仕様はindexOfと同じです。
要素の有無を調べるcontainsメソッド
あるデータがリストに含まれているかを調べたいが、要素が格納されているインデックス番号までは必要ない場合はcontainsメソッドが使えます。
boolean contains(Object o)
containsメソッドなら、引数で指定したデータがリストに1つでも含まれていればtrue、含まれていなければfalseを返すのでデータの存在チェックが簡潔に実装できます。
リストの要素数を取得するsizeメソッド
sizeメソッドを使うと、リストが要素をいくつ持っているかを取得できます。
int size()
「要素を先頭から順番に取得する方法」のところで、Java 5 から実装されている拡張for文を例に挙げました。Java 5 未満でも、sizeメソッドを使用すれば for文でリスト要素を先頭から順番に取得することができます。
List data = new ArrayList();
for ( int idx = 0; idx < data.size(); idx++) {
String element = (String)data.get(idx);
任意の処理
}
リストの要素すべてをコピーするcloneメソッド
cloneメソッドを使うとオブジェクトのコピーを作成することができます。
cloneは厳密にはListが持つメソッドではありません。Javaの全クラスが親とする Object クラスが持っているメソッドです。
List dataList = new List();
List copiedList = dataList.clone();
cloneメソッドを使えば、全ての要素を移し替えなくても簡単にコピーを作成できます。
シャローコピーとディープコピー
Javaには、シャローコピーとディープコピーの2種類のコピー方式があります。cloneを使ってリストをコピーする場合、コピーはシャローコピーで行われる事に注意が必要です。
Javaでオブジェクトの代入はオブジェクトの参照コピーなので、コピー元で値を変更するとコピー先の値も変更されてしまいます。リストのシャローコピーではリスト要素は参照コピーされるので、同じ事が起こります。
リストの一部をコピーするsublistメソッド
sublistメソッドは、リストから要素の一部を切り出した別のリストを作成したい場合に使用します。
List<E> subList(int fromIndex, int toIndex)
引数のfromIndexで指定した要素番号から、toIndexで指定した要素番号のひとつ手前までを切り出したリストの参照が戻り値として返されます。
sublistで切り出したリストは参照専用とするべき
sublistでリストがコピーされたように見えますが、実際は元のリストから要素が切り出されたビューです。sublistで切り出したリストに対して追加、変更、削除などの操作をした場合、切り出し元のリストに対しても変更が反映されてしまいます。
元のリストを変更したい場合は元のリストを直接操作し、切り出したリストのみを操作したいのであればリストをコピーした後でコピーを操作する方が間違いありません。
リストの概要と基本的な使い方のまとめ
Javaのリストはデータの追加や削除など、配列ではできないデータ操作ができる便利なクラスです。
リストは、ListインタフェースやArrayListやLinkedListなどの実装クラスが用意されていて、用途に応じて使い分けることで、汎用的な実装や処理効率を高めることができます。
Javaのリストには多くのメソッドが用意されているので、基本的なメソッドを理解しいつでも使えるようにしておきましょう。