新人エンジニア研修で知っておきたいArrayListの使い方
なぜ、ArrayListの理解が重要なのか、その理由
この記事では、弊社の新人エンジニア研修の参考にJavaを解説します。
前回は例外処理について解説しました。
今回はコレクションとジェネリクスについて解説します。
私たちは既に配列を学んでいます。配列のメリットは、同じ型のデータをまとめられる点にありました。繰り返し処理と組み合わせて、複数の要素に対して一気に処理をするということも容易でした。
しかし、配列にはデメリットもありました。それは、要素の数をあらかじめ指定しなければならないということです。
もしも、要素の数にあわせて伸び縮みする配列があったらさらに便利だと思いませんか?
1.ArrayList
ArrayList(標準API)は要素の数にあわせて伸び縮みする配列で便利なメソッドを持っています。
標準APIには次のような解説があります。
Listインタフェースのサイズ変更可能な配列の実装です。(中略)
※Listインタフェースという言葉も後で関係するので心の片隅にとどめておいてください。
【Array:配列】【List:リスト】という意味のクラスです。クラスですから配列とは違い便利なメソッドも持っています。デメリットは配列よりもパフォーマンスが多少良くない位ですからこれを使わない手はないですね。実際、実務では配列よりも良く使われているくらいです。
ただし、少しだけ面倒な点として、配列に格納するオブジェクトの型を指定する必要があります。配列や変数に何が入っているかわからないという状況は、ClassCastExceptionを起こしやすいことは皆さんもこれまでの研修で理解いただけているものと思います。このときの型の指定を「型パラメータ」といいます。
下図は文字列を格納できるArrayListのイメージ図です。

配列同様、indexは0始まりだということに着目してください。
以下のExample01はArrayListを使うサンプルです。
package chap13;
import java.util.ArrayList;
public class Example01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
list.add("ghi");
System.out.println(list.get(0));
}
}
<実行結果>
abc |
このように、
ArrayList<型パラメータ> 変数名 = new ArrayList<>()
と書きます。
<型パラメータ>を指定するのはなぜでしょうか?
その理由は、以下Example02のように数値を入れようとしてもコンパイルが通りませんので、未然にトラブルを防ぐことができるからです。型の安全性が確保されるのです。
package chap13;
import java.util.ArrayList;
public class Example02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// list.add(1);
System.out.println(list.get(0));
}
}
2.ジェネリクス
もう一度ArrayList(標準API)を見ると
クラスArrayList<E>
と書かれています。
このEが型パラメータです。パラメータというのは変数という意味でした。「型が変わり得る」ということで型パラメータというのです。型パラメータの文字は“E”でなくても何でも良いのですが、ArrayListの場合は、【Element:要素】の頭文字のEを使うのが慣習になっています。そして、Elementにクラスを指定することで、ArrayListの要素を限定することができるのです。
ArrayListを宣言する時に以下のように記述すると要素の型は何でもいいArrayListになってしまいます。
ArrayList list = new ArrayList();
つまり、これは以下の宣言と同じ意味になります。
ArrayList<Object> list = new ArrayList<>();
そうすると、以下のようにArrayListから要素を取り出して処理をする際にキャスト(やinstanceOfを用いた条件分岐)が頻発する扱いづらいプログラムになってしまいます。
package chap13;
import java.util.ArrayList;
public class Example03 {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add(1);
list.add("文字列");
int num = (Integer) list.get(0);
String str = (String) list.get(1);
System.out.println(num);
System.out.println(str);
}
}
<実行結果>
1 文字列 |
IDEの機能を駆使してJavaのソースコードからArrayListクラスのget()メソッドを探してください。
426行目です。
Java SE 17 の標準APIpublic E get(int index) { Objects.checkIndex(index, size); return elementData(index); }
ここで注目いただきたいのは戻り値の型です。この“E”というのは要素【Element】という意味でした。
今、皆さんがArrayListの要素に入れたいクラスの型を、過去にJavaを作った人が予測することは可能だったでしょうか?
もちろん不可能ですね。
ですから、抽象的に“E”としておいて、あとは我々がArrayListを作る際に要素の型を決めることになっているのです。このような仕組みをジェネリクス【Generics】といいます。ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、様々な型に対応する汎用的なクラスやメソッドを設計できる機能です。いろいろな可能性のある型を一般化しているため日本語では総称型と呼ばれることもあります。英語会話でも【Generaly speaking】といえば「一般的に言えば」という意味ですね。
3.ArrayListではラッパークラスを使う
一点注意しないといけないのは、この型パラメータに格納できるのは「オブジェクトだけ」だ、という点です。プリミティブ型は格納できません。プリミティブ型の代わりにラッパークラスを使います。
ラッパークラスとは、プリミティブデータ型をオブジェクトとして扱うためのクラスです。intではなくInteger、doubleではなくDoubleとなります。サランラップと同じようにプリミティブデータ型を「包む」(wrap)ことからラッパーの名前があります。
ただし、プリミティブ型とラッパークラスの間で自動的に変換されるオートボクシング、オートアンボクシングによってそれほど不便は感じません。
以下のExample04はラッパークラスを使う例です。直感的に使えていますね。
package chap13;
import java.util.ArrayList;
public class Example04 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(0) + list.get(1) + list.get(2));
}
}
<実行結果>
6 |
4.ArrayListをフィールドに持つクラス
ArrayListをフィールドとして持つクラスには大きなメリットがあります。
まず、ArrayListは可変長のリストであり、アイテムの追加や削除が柔軟に行えます。以下のCartクラスでは商品の追加(addItem)、一覧取得(getItems)、サイズ取得(getSize)、クリア(clear)といった操作が簡潔に実装されており、カート機能を持つアプリケーションにおいて非常に実用的です。
また、データのカプセル化により、内部のリスト構造が他のクラスに直接触れられないよう保護されており、保守性と安全性の高い設計になっています。このように、ArrayListをフィールドに持つことで、柔軟なデータ操作と堅牢なクラス設計が可能になります。
package com.example.demo.model;
import java.util.ArrayList;
import java.util.List;
public class Cart {
private List<String> items = new ArrayList<>();
public List<String> getItems() {
return items;
}
public void addItem(String car) {
items.add(car);
}
public int getSize() {
return items.size();
}
public void clear() {
items.clear();
}
}
5.コレクションフレームワーク
このようにJavaには、ArrayList以外にも複数のオブジェクトを格納するための便利なクラスがたくさんあります。
オブジェクトの集合をコレクションといい、そのコレクションを操作するために用意されたJavaの標準APIをコレクションフレームワークといいます。
この解説記事では、新人エンジニア研修の時間の関係でArrayListとLinkedListだけですが、要素に重複を許したくないときに使うHashSet、要素を素早く取り出せる、また、キーワードを使って取り出せるHashMapなど、いろいろとあります。
このようにオブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークといいました。ですので、ArrayListやLinkedListも当然コレクションフレームワークです。
実は、下図のようにListインタフェースのスーパーインタフェースにCollectionというインタフェースがあるのです。

また、機会があれば他のコレクションフレームワークについてもお話ししたいと思います。しかし、この新人エンジニア研修では確実にArrayListをマスターしましょう。ArrayListをマスターすれば、他のコレクションフレームワークにもある程度応用が利きます。
Listインタフェースのメソッドをいろいろと試してみましょう。そしてそのメソッドの内容をチーム内で共有しておいてください。
6.ConcurrentModificationException
なお、拡張for文の中でArrayListの要素を削除するとConcurrentModificationExceptionが起こります。
以下のExample07を実行してみてください。
package chap13;
import java.util.ArrayList;
public class Example07 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("alice");
names.add("bob");
names.add("chris");
for (String str : names) {
if (str.equals("alice")) {
names.remove(str);
}
}
System.out.println(names.toString());
}
}
対策としては以下のExample08のようにイテレータに変換してから削除する方法もあります。
Javaのイテレータ【Iterator】は、コレクション【Collection】オブジェクトの要素に順次アクセスするためのインターフェースです。イテレータを使用することで、コレクション内の要素を逐次的に取り出すためのメソッドが使えるようになります。イテレータを利用することで、要素の追加・変更・削除を安全に行うことができます。
package chap13;
import java.util.ArrayList;
import java.util.Iterator;
public class Example08 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("alice");
names.add("bob");
names.add("chris");
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if (str.equals("alice")) {
iterator.remove();
}
}
System.out.println(names.toString());
}
}
まとめができたら、アウトプットとして演習問題にチャレンジしましょう。
以上、今回は「ArrayListでもっと配列を便利に使いこなす」方法について見てきました。
IT企業向け新人研修おすすめ資料 無料公開中 (saycon.co.jp)