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

配列同様、indexは0始まりだということに着目してください。
以下のサンプルプログラムを見てください。
<実行結果>
abc |
このように、
ArrayList<型パラメータ> 変数名 = new ArrayList<>()
と書きます。
<型パラメータ>を指定するのはなぜでしょうか?
その理由は、以下のように数値を入れようとしてもコンパイルが通りませんので、未然にトラブルを防ぐことができるからです。
2.ジェネリクス
もう一度ArrayList(標準API)を見ると
クラスArrayList<E>
と書かれています。
このEが型パラメータです。パラメータというのは変数という意味でした。「型が変わり得る」ということで型パラメータというのです。
Elementにクラスを指定することで、ArrayListの要素を限定することができるのです。型パラメータの文字は“E”でなくても何でも良いのですが、ArrayListの場合は、Element「要素」の頭文字のEを使うのが慣習になっています。
ArrayListを宣言する時に以下のように記述すると要素の型は何でもいいArrayListになってしまいます。
ArrayList list = new ArrayList();
つまり、これは以下の宣言と同じ意味になります。
ArrayList<Object> list = new ArrayList<>();
そうすると、以下のようにArrayListから要素を取り出して処理をする際にキャスト(やinstanceOfを用いた条件分岐)が頻発するプログラムになってしまいます。
<実行結果>
1 文字列 |
IDEの機能を駆使してJavaのソースコードからArrayListクラスのgetメソッドを探してください。
434行目です。
public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); }
ここで注目いただきたいのは戻り値の型です。
“E”というのは要素(Element)という意味でした。
今、皆さんがArrayListの要素に入れたいクラスの型を、過去にJavaを作った人が予測することは可能だったでしょうか?
もちろん不可能ですね。
ですから、抽象的に“E”としておいて、あとは我々がArrayListを作る際に要素の型を決めることになっているのです。このような仕組みをジェネリクス(Generics)といいます。
ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、様々な型に対応する汎用的なクラスやメソッドを設計できる機能です。
いろいろな可能性のある型を一般化しているため日本語では総称型と呼ばれることもあります。英語会話でも「Generaly speaking」といえば「一般的に言えば」という意味ですね。
3.ArrayListではラッパークラスを使う
一点注意しないといけないのは、この型パラメータに格納できるのは「オブジェクトだけ」だ、という点です。プリミティブ型は格納できません。
プリミティブ型の代わりに12.抽象クラスでみたラッパークラスを使います。ただし、オートボクシング、オートアンボクシングによってそれほど不便は感じません。
以下のサンプルプログラムを見てください。
<実行結果>
6 |
4.Listインタフェース
実は皆さんが目にするArrayListのプログラムは上記のように
ArrayList<String> list = new ArrayList<>();
と宣言されているものよりも
List<String> list = new ArrayList<>();
のような宣言のされ方をしたものが多いと思います。
例えば以下のサンプルプログラムのように。
<実行結果>
pencil:100 eraser:60 note:80 |
なぜ、このような書き方ができるのかというと、ArrayListクラスがListインタフェースを実装しているからですね。インタフェースで学んだようにインタフェースを実装したクラスのインスタンスはインタフェース型の参照に代入できるのでした。
では、なぜこのような書き方をするのかというと、プログラム中でListインタフェースを実装した他のクラスに付け替えることができるのです。例えば、同じListインタフェースを実装しているLinkedListクラスに付け加えることで配列の先頭要素の削除は高速になります。
このあたりの継承関係は少し複雑なのですが、この記事に関連する部分だけをピックアップすると下図15.2のようになっています。

上図にあるLinkedListは「アルゴリズムとデータ構造」で学ぶ双方向リストです。そのため、削除の際にはポインタの付替えだけで済むため、要素の移動がなく高速なのです。
それに対して、ArrayListは内部的には配列を利用したリスト構造であるため先頭要素を削除すると、配列を前に詰めるようにコピーするため遅いのです。(下図15.3参照)
次のサンプルプログラムで検証してみましょう。
実験のアイディアは、リストに10,000の要素(pencil)を詰めた配列を用意して、前半の半分の5,000個はArrayListとして、後半の半分の5,000個はLinkedListとして、配列の先頭要素から削除(remove)していって、すべての要素を削除し終わった時間をナノ秒単位で計測して比較するというものです。
計測にはナノ秒単位まで測れるSystem.nanoTimeメソッドを使っています。
また、以下のコードでArrayListをLinkedListに変換しています。
List<Product> list2 = new LinkedList<>(list1);
このようにコンストラクタを使って付け替えを行っている箇所を読み取って下さい。
<結果の例>
ArrayListからの削除 5,531,600ナノ秒かかりました LinkedListからの削除 440,100ナノ秒かかりました |
このようなことができるのもArrayListとLinkedListが両方ともListインタフェースを実装しているからです。そして、どちらのコンストラクタも引数としてList型を受け取れるようになっているからです。
ポリモーフィズムを使った引数設計でしたね。
たとえるなら、「お母さんアレ取って」と言って理解しあえる夫婦のようなものでした。アレーリストなだけに。。。
ただし、繰り返しになりますが、動的な配列ではArrayListを使えば大丈夫で、LinkedListの活用場面はそれほどありません。

5.コレクションフレームワーク
このようにJavaには、ArrayList以外にも複数のオブジェクトを格納するための便利なクラスがたくさんあります。
オブジェクトの集合をコレクションといい、そのコレクションを操作するために用意されたJavaの標準APIをコレクションフレームワークといいます。
この解説記事では、新人エンジニア研修の時間の関係でArrayListとLinkedListだけですが、
要素に重複を許したくないときに使うHashSet
要素を素早く取り出せる、また、キーワードを使って取り出せるHashMap
など、いろいろとあります。
このようにオブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークといいます。ですので、ArrayListやLinkedListも当然コレクションフレームワークです。
実は、下図15.4のようにListインタフェースのスーパーインタフェースにCollectionというインタフェースがあるのです。

また、機会があれば他のコレクションフレームワークについてもお話ししたいと思います。しかし、この新人エンジニア研修では確実にArrayListをマスターしましょう。ArrayListをマスターすれば、他のコレクションフレームワークにも応用が利きます。
Listインタフェースのメソッドをいろいろと試してみましょう。そしてそのメソッドの内容をチーム内で共有しておいてください。
<まとめ:隣の人に正しく説明できたらチェックを付けましょう>
□ ArrayListはArrayListは要素の数にあわせて伸び縮みする配列で便利なメソッドを持っている
□ ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、様々な型に対応する汎用的なクラスやメソッドを設計できる機能である
□ 型パラメータに格納できるのはオブジェクトだけである
□ オブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークという
まとめができたら、アウトプットとして演習問題にチャレンジしましょう。
【今回の復習Youtube】
068-コレクションフレームワーク-ArrayListの単純なサンプル
069-コレクションフレームワーク-ArrayListにオブジェクトを格納する
以上、今回は「ArrayListでもっと配列を便利に使いこなす」方法について見てきました。
次回は、「日付/時刻と列挙型でますます広がるJavaのクラス」です。
これまでお話しできなかったクラスについても時間が許す限り説明しますね。次回を楽しみに待っていてください。