Javaでは、ArrayListはListインターフェースを実装したクラスです。ArrayListクラスはオブジェクトのリストを配列に格納することができ、リスト内の要素の追加、削除、アクセスを行うためのメソッドを提供します。
ListインターフェースはJava Collections Frameworkの一部であり、要素の順序付きコレクションを表します。これは、Collectionインターフェースを拡張し、オブジェクトのコレクション(List, Set, Mapなど)を管理するための標準的な方法を提供しています。
JavaにおけるArrayListと通常の配列の大きな違いは、ArrayListが動的であることです。つまり、リストの変更に対応して、必要に応じてサイズを拡大または縮小することができます。
整数値を格納するための動的配列は、以下のように宣言することができます。
ArrayList<Integer> array = new ArrayList<Integer>();
new ArrayList<Integer>()の部分でArrayListクラスの新しいインスタンスが作成されます。
<Integer>の部分は型パラメータと呼ばれ、このArrayListがInteger型の要素を格納することを指定します。Integerクラスは,Javaのプリミティブなデータ型であるint型のラッパークラスです。
型パラメータはJavaのジェネリクスと呼ばれるもので、クラスやインターフェースが扱うことのできる要素の型を指定することができるようになっています。<>はダイアモンド演算子といい、左の変数にダイアモンド演算子で型パラメータが指定されていれば、右側のダイアモンド演算子の中の型パラメータは省略できます。
最後にある()は、ArrayListクラスのコンストラクタを呼び出して、クラスの新しいインスタンスを作成するために使用されます。コンストラクタは、newキーワードで新しいオブジェクトを作成するときに呼び出される特別なメソッドでした。
array変数は作成されるArrayListオブジェクトへの参照です。array.add(3)でリストの最後に整数の3を追加したり、array.get(0)でリストの最初の位置の要素を取得するなど、このオブジェクトのメソッドを呼び出すために使用します。
それでは、例を見てみましょう。
// java.util.ArrayListをimportします
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// Stringクラスで構成される動的配列
ArrayList<String> names = new ArrayList<>();
names.add("Steve");
names.add("Alice");
names.add("Bob");
// ArrayListの場合、ArrayListクラスのsuperクラスであるAbstractCollectionクラスが持つtoString()メソッドがprintlnによって呼び出され、ArrayListの文字列表現を取得します
System.out.println(names);
names.remove("Alice");
System.out.println(names);
// 指定したインデックスの要素を別の要素に置換する
names.set(0, "Smith");
System.out.println(names);
}
}
ArrayListのサイズを計算するには、ArrayListクラスのsizeメソッドを使用します。このメソッドは、リスト内の要素数を返します。リストが空かどうかを調べるには、isEmptyメソッドを使用することもできます。このメソッドは、リストが空の場合はtrueを、そうでない場合はfalseを返します。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// Stringクラスで構成される動的配列
ArrayList<String> names = new ArrayList<>();
names.add("Steve");
// ArrayListのサイズを取得する
System.out.println(names.size());
// 空かどうかチェックする
System.out.println(names.isEmpty());
names.remove(0);
System.out.println(names.isEmpty());
}
}
ArrayListコンストラクタは、リストやセットなどの要素のコレクションである単一の引数を受け取ることができ、動的配列の初期化として使用することができます。ArrayクラスのasListメソッドは、単一の要素、複数の要素、または要素の配列を受け取り、固定サイズのリストを返すことから、以下のように組み合わせることができます。
ただ、注意すべきなのはasListメソッドで初期化した動的配列は、Arraysクラスが内部に持つArrayListクラスで固定配列をラップしているだけです。そのためjava.util.ArrayListとは異なり、要素の変更は出来ても、追加や削除ができません。それを更にArrayListでラップすることでArrayListが持つメソッドを利用できるようになります。
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// Arrays.asList()でリストを作成するとそのままでは要素の追加や削除ができません。
List<Integer> list = Arrays.asList(1,2,3);
System.out.println(list.getClass().getName()); // java.util.Arrays$ArrayList
// list.add(4); // できない
System.out.println(list);
// ArrayListでラップすることでadd()メソッドが使用できます。
List<Integer> listWrapped = new ArrayList<>(Arrays.asList(1,2,3));
System.out.println(listWrapped.getClass().getName()); // java.util.ArrayList
listWrapped.add(4); // できる
System.out.println(listWrapped);
ArrayList<String> names = new ArrayList<String>(Arrays.asList("Steve","Alice","Bob"));
System.out.println(names);
}
}
ArrayListを固定長の配列に変換するには、複数の方法があります。
まずはforループを使ってひとつずつコピーしていく方法を見てみましょう。ArrayListクラスはjava.utilパッケージの一部であり、プリミティブ型ではなくオブジェクトを保持するために設計されているため、固定長配列へ変換する際にデータ型に注意する必要があります。
プリミティブ型の配列へ変換するときには、forループを使って1つずつコピーすると自動でラッパークラスからプリミティブへ変換します。これをオートアンボクシングと言います。
import java.util.ArrayList;
public class Main {
public static void printIntArray(int[] arr) {
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
// 固定長配列を用意
int[] array = new int[list.size()];
// 1つ1つ固定長配列にコピー オートアンボクシング機能で、Integerからintへ自動で変換します
for (int i = 0; i < list.size(); i++) {
// ArrayListのgetメソッドは、インデックスの要素を取得可能
array[i] = list.get(i);
}
// 固定長配列を表示する関数を実行
printIntArray(array);
}
}
別の方法としてtoArrayメソッドを使う方法があります。toArrayメソッドは、引数として希望する型の配列を受け取り、コレクション内のすべての要素を含む配列を返します。指定された配列がすべての要素を保持するのに十分な大きさがない場合、新しい配列が作成されて返されます。toArrayメソッドはプリミティブ型配列には使えません。
import java.util.ArrayList;
public class Main {
public static <T> void printArray(T[] arr) {
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
}
public static void main(String[] args) {
// Stringの場合
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
// toArrayメソッドは、作成したい配列の型を1つの引数として受け取ります。
// この場合、ArrayListと同じサイズの文字列配列のインスタンスを渡します。
// toArrayメソッドは、リストオブジェクトと同じサイズの固定長配列を作成し、リストのすべての要素を配列にコピーします。
String[] array = list.toArray(new String[list.size()]);
printArray(array);
// Integerの場合
ArrayList<Integer> listInt = new ArrayList<>();
listInt.add(1);
listInt.add(2);
listInt.add(3);
listInt.add(4);
Integer[] arrayInt = listInt.toArray(new Integer[listInt.size()]);
printArray(arrayInt);
}
}
動的配列は、以下のように宣言することもできます。
List<Integer> array = new ArrayList<Integer>();
使用する際は、ArrayListだけでなくjava.util.Listもimportします。両者の違いは、List<Integer>型で宣言された場合、ArrayListクラスでのみ定義されているメソッドは利用できませんが、ArrayList<Integer>型で宣言された場合、ArrayList<Integer>とその上位(Listも含めて)で宣言されているメソッドを使用することができます。
実際にはList<Integer>型で宣言された場合の方がより柔軟性があり、後でArrayList<Integer>ではなく、LinkedList<Integer>などの同じListインターフェースを実装したクラスを使用したいときに、設計をより簡単に変更することができます。この場合の柔軟性とは、List型を引数に取る関数があった場合、ArrayListでもLinkedListでもStackでも受け取ることができることを意味しています。
インターフェースについては、プログラミングパラダイム/OOP/抽象クラス、インターフェース、ジェネリクスで詳しく学習できます。