この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。
前回は例外処理について解説しました。
今回はコレクションとジェネリクスについて解説します。
私たちは既に配列を学んでいます。
配列のメリットは、同じ型のデータをまとめられる点にありました。
繰り返し処理と組み合わせて、複数の要素に対して一気処理をするということも容易でした。
しかし、配列にはデメリットもありました。
それは、要素の数をあらかじめ指定しなければならないということです。
もしも、要素の数にあわせて伸び縮みする配列があったらさらに便利だと思いませんか?
1.ArrayList
ArrayListといいます。
APIには次のような解説があります。
Listインタフェースのサイズ変更可能な配列の実装です。(中略)
Listインタフェースという言葉も後で関係するので心の片隅にとどめておいてください。
Array(配列)List(リスト)という意味のクラスです。
クラスですから配列とは違い便利なメソッドも持っています。
デメリットは配列よりもパフォーマンスが多少良くないくらいですからこれを使わない手はないですね。
実際、実務では配列よりも良く使われています。
ただし、少しだけ面倒な点として、配列に格納するオブジェクトの型を指定する必要があります。
配列や変数に何が入っているかわからないという状況は、ClassCastExceptionを起こしやすいことは皆さんもこれまでの研修で理解いただけているものと思います。
このときの型の指定を型パラメータといいます。
以下のサンプルプログラムを見てください。
文字列を格納できるArrayListの例です。
配列同様、indexは0始まりだということに着目してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package chap15; 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<>() |
と書きます。
<型パラメータ>を指定するのはなぜでしょうか?
ここで、以下のように数値を入れようとしてもコンパイルが通りませんので、未然にトラブルを防ぐことができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
package chap15; import java.util.ArrayList; public class Ref01 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add(1); System.out.println(list.get(0)); } } |
2.ジェネリクス
もう一度APIを見ると
クラスArrayList<E>
と書かれています。
このEが型パラメータです。
Elementにクラスを指定することで、ArrayListの要素を限定することができるのです。
型パラメータも文字は何でも良いのですが、ArrayListnの場合は、Element(要素)の頭文字のEを使うのが慣習になっています。
ArrayListを宣言する時に以下のように記述すると要素は何でもいいArrayListになってしまいます。
ArrayList list = new ArrayList(); |
つまり、これは以下の宣言と同じ意味になります。
ArrayList<Object> list = new ArrayList<>(); |
そうすると、以下のようにArrayListから要素を取り出して処理をする際にキャスト(やinstanceOfを用いた条件分岐)が頻発するプログラムになってしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package chap15; import java.util.ArrayList; public class Ref03 { 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 + str); } } |
<結果>
1文字列 |
IDEの機能を駆使してJavaのソースコードからArrayListのgetメソッドを探してください。
4344 4345 4346 4347 4348 |
public E get(int index) { rangeCheck(index); return elementData(index); } |
ここで注目いただきたいのは戻り値の型です。
Eとなっているので要素という意味でした。
皆さんがArrayListの要素に入れたいクラスの型をJavaを作った人が事前に予測することは可能だったでしょうか?
もちろん不可能ですね。
ですから、抽象的にEとしておいて、あとは我々がArrayListを作る際に要素の型を決めることになっているのです。
このような仕組みをジェネリクス(Generics)といいます。
ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、様々な型に対応する汎用的なクラスやメソッドを設計できる機能です。
英語会話でも「Generaly speaking」といえば「一般的に言えば」という意味ですね。
いろいろな可能性のある型を一般化しているため日本語では総称型と呼ばれることもあります。
3.プリミティブ型の代わりにラッパークラスを使う
一点注意しないといけないのは、この型パラメータに格納できるのは「オブジェクトだけ」だ、という点です。
プリミティブ型は格納できません。
プリミティブ型の代わりに12.抽象クラスでみたラッパークラスを使います。
ただし、ボクシング、アンボクシングによってそれほど不便は感じません。
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package chap15; import java.util.ArrayList; public class Example02 { 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.Listインタフェース
実は皆さんが目にするArrayListのプログラムは上記のように
ArrayList<String> list = new ArrayList<>(); |
と宣言されているものよりも
List<String> list = new ArrayList<>(); |
のような宣言のされ方をしたものが多いと思います。
例えば以下のサンプルプログラムのように。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package chap15; import java.util.ArrayList; import java.util.List; public class Example03 { public static void main(String[] args) { List<Product> list = new ArrayList<>(); list.add(new Product("pencil", 100)); list.add(new Product("eraser", 60)); list.add(new Product("note", 80)); for (Product product : list) { System.out.println(product); } } } class Product { private String productName; private int price; public Product(String productName, int price) { this.productName = productName; this.price = price; } public String toString() { return this.productName + ":" + this.price; } } |
<結果>
pencil:100 eraser:60 note:80 |
なぜ、このような書き方ができるのかというと、ArrayListクラスがListインターフェースを実装しているからですね。
インターフェースで学んだようにインターフェースを実装したクラスのインスタンスはインターフェース型の参照に代入できるのでした。
では、なぜこのような書き方をするのかというと、プログラム中でListインターフェースを実装した他のクラスに付け替えることができるのです。
例えば、同じListインターフェースを実装しているLinkedListクラスに付け加えることで配列の先頭要素の削除は高速になります。
LinkedListはアルゴリズムとデータ構造で学ぶ双方向リストです。
そのため、削除の際にはポインタの付替えだけで済むため、要素の移動がなく高速なのです。
それに対して、ArrayListは内部的には配列を利用したリスト構造であるため先頭要素を削除すると、配列を前に詰めるようにコピーするため遅いのです。

ArrayListとLinkidListの比較
次のサンプルプログラムで検証してみましょう。
実験のアイディアは、リストに10,000の要素を詰めた配列を用意して、前半の半分はArrayListとして、後半の半分はLinkedListとして、配列の先頭要素を削除(remove)し、各々の時間をナノ秒単位で計測(System.nanoTimeメソッド)するというものです。
色を付けてある部分でコンストラクタを使って付け替えを行っているのが分かりますか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class Example04 { public static void main(String[] args) { List<Product> list1 = new ArrayList<>(); long startTime, endTime; for (int i = 0; i < 10000; i++) { list1.add(new Product("pencil", 100)); } startTime = System.nanoTime(); for (int i = 0; i < 5000; i++) { list1.remove(0); } endTime = System.nanoTime(); System.out.printf("%s%,7d%s", "ArrayListからの削除\t",(endTime - startTime), "ナノ秒かかりました"); System.out.println(""); <span style="background-color: #ffff00;">List<Product> list2 = new LinkedList<>(list1);</span> startTime = System.nanoTime(); for (int i = 0; i < 5000; i++) { list2.remove(0); } endTime = System.nanoTime(); System.out.printf("%s%,9d%s", "LinkedListからの削除\t",(endTime - startTime), "ナノ秒かかりました"); } } |
<結果の例>
ArrayListからの削除 5,531,600ナノ秒かかりました LinkedListからの削除 440,100ナノ秒かかりました |
このようなことができるのもArrayListとLinkedListが両方ともListインターフェースを実装しているからです。
そして、どちらのコンストラクタも引数としてList型を受け取れるようになっているからです。
ポリモーフィズムを使った引数設計でしたね。
たとえるなら、「お母さんアレ取って」と言って理解しあえる夫婦のようなものでした。
アレーリストなだけに。。。
ただし、繰り返しになりますが、動的な配列ではArrayListを使えば大丈夫で、LinkedListの活用場面はそれほどありません。
5.コレクションフレームワーク
このようにJavaには、ArrayList以外にも複数のオブジェクトを格納するための便利なクラスがたくさんあります。
この解説記事では、新人研修の時間の関係でArrayListとLinkedListだけですが、
要素に重複を許したくないときに使うHashSet
要素を素早く取り出せる、また、キーワードを使って取り出せるHashMap
など、いろいろとあります。
このようにオブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークといいます。
また、機会があれば他のコレクションフレームワークについてもお話ししたいと思います。
ここでは、確実にArrayListをマスターしましょう。
他のコレクションフレームワークにも応用が利きますので。
Listインターフェースのメソッドをいろいろと試してみましょう。
そしてそのメソッドの内容をチーム内で共有しておいてください。
今回はコレクションフレームワークとジェネリクスについて見てきました。
次回のテーマは日付/時刻などその他APIです。
これまでお話しできなかったクラスについても説明しますね。
ということで、次回を楽しみに待っていてください。
【今回の復習Youtube】
068-コレクションフレームワーク-ArrayListの単純なサンプル
069-コレクションフレームワーク-ArrayListにオブジェクトを格納する
JavaSE8の解説に戻る