Print Friendly, PDF & Email

新人エンジニア研修で知っておきたいArrayListの使い方

なぜ、ArrayListの理解が重要なのか、その理由。

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回は例外処理について解説しました。

今回はコレクションとジェネリクスについて解説します。

私たちは既に配列を学んでいます。

配列のメリットは、同じ型のデータをまとめられる点にありました。

繰り返し処理と組み合わせて、複数の要素に対して一気に処理をするということも容易でした。

しかし、配列にはデメリットもありました。

それは、要素の数をあらかじめ指定しなければならないということです。

もしも、要素の数にあわせて伸び縮みする配列があったらさらに便利だと思いませんか?

実はそんなクラスがあるのです。

1.ArrayList

ArrayList(標準API)は要素の数にあわせて伸び縮みする配列で便利なメソッドを持っています。

標準APIには次のような解説があります。

Listインタフェースのサイズ変更可能な配列の実装です。(中略)

※Listインタフェースという言葉も後で関係するので心の片隅にとどめておいてください。

Array「配列」List「リスト」という意味のクラスです。

クラスですから配列とは違い便利なメソッドも持っています。

デメリットは配列よりもパフォーマンスが多少良くない位ですからこれを使わない手はないですね。

実際、実務では配列よりも良く使われています。

ただし、少しだけ面倒な点として、配列に格納するオブジェクトの型を指定する必要があります。

配列や変数に何が入っているかわからないという状況は、ClassCastExceptionを起こしやすいことは皆さんもこれまでの研修で理解いただけているものと思います。

このときの型の指定を「型パラメータ」といいます。

文字列を格納できるArrayListのイメージ図です。

ArrayListのイメージ
ArrayListのイメージ

配列同様、indexは0始まりだということに着目してください。

以下のサンプルプログラムを見てください。

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<>()

と書きます。

<型パラメータ>を指定するのはなぜでしょうか?

その理由は、以下のように数値を入れようとしてもコンパイルが通りませんので、未然にトラブルを防ぐことができるからです。

package chap15;

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が型パラメータです。

パラメータというのは変数という意味でした。

「型が変わりうる」ということで型パラメータというのです。

Elementにクラスを指定することで、ArrayListの要素を限定することができるのです。

型パラメータの文字は“E”でなくても何でも良いのですが、ArrayListの場合は、Element「要素」の頭文字のEを使うのが慣習になっています。

ArrayListを宣言する時に以下のように記述すると要素の型は何でもいいArrayListになってしまいます。

ArrayList list = new ArrayList();

つまり、これは以下の宣言と同じ意味になります。

ArrayList<Object> list = new ArrayList<>();

そうすると、以下のようにArrayListから要素を取り出して処理をする際にキャスト(やinstanceOfを用いた条件分岐)が頻発するプログラムになってしまいます。

package chap15;

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メソッドを探してください。

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.抽象クラスでみたラッパークラスを使います。

ただし、オートボクシング、オートアンボクシングによってそれほど不便は感じません。

以下のサンプルプログラムを見てください。

package chap15;

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.Listインタフェース

実は皆さんが目にするArrayListのプログラムは上記のように

ArrayList<String> list = new ArrayList<>();

と宣言されているものよりも

List<String> list = new ArrayList<>();

のような宣言のされ方をしたものが多いと思います。

例えば以下のサンプルプログラムのように。

package chap15;

import java.util.ArrayList;
import java.util.List;

public class Example05 {

    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);
        }
    }
}
package chap15;

public class Product {

    private String productName;
    private int price;

    public Product(String productName, int price) {
        this.productName = productName;
        this.price = price;
    }

    @Override
    public String toString() {
        return this.productName + ":" + this.price;
    }
}

<結果を表示する>

pencil:100
eraser:60
note:80

なぜ、このような書き方ができるのかというと、ArrayListクラスがListインターフェースを実装しているからですね。

インターフェースで学んだようにインターフェースを実装したクラスのインスタンスはインターフェース型の参照に代入できるのでした。

では、なぜこのような書き方をするのかというと、プログラム中でListインターフェースを実装した他のクラスに付け替えることができるのです。

例えば、同じListインターフェースを実装しているLinkedListクラスに付け加えることで配列の先頭要素の削除は高速になります。

このあたりの継承関係は少し複雑なのですが、この記事に関連する部分だけをピックアップすると以下のようになっています。

ArrayListとLinkedListの関係
ArrayListとLinkedListの関係

LinkedListは「アルゴリズムとデータ構造」で学ぶ双方向リストです。

そのため、削除の際にはポインタの付替えだけで済むため、要素の移動がなく高速なのです。

それに対して、ArrayListは内部的には配列を利用したリスト構造であるため先頭要素を削除すると、配列を前に詰めるようにコピーするため遅いのです。

ArrayListとLinkedListの違い
ArrayListとLinkedListの違い

次のサンプルプログラムで検証してみましょう。

実験のアイディアは、リストに10,000の要素(pencil)を詰めた配列を用意して、前半の半分の5,000個はArrayListとして、後半の半分の5,000個はLinkedListとして、配列の先頭要素から削除(remove)していって、すべての要素を削除し終わった時間をナノ秒単位で計測して比較するというものです。

計測には名ナノ秒単位まで測れるSystem.nanoTimeメソッドを使っています。

また、以下のコードでArrayListをLinkedListに変換しています。

List<Product> list2 = new LinkedList<>(list1);

このようにコンストラクタを使って付け替えを行っている箇所を読み取って下さい。

package chap15;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Example06 {

    public static void main(String[] args) {

        List<Product> list1 = new ArrayList<>();
        long startTime, endTime;

        for (int i = 0; i < 10_000; i++) {
            list1.add(new Product("pencil", 100));
        }

        startTime = System.nanoTime();
        for (int i = 0; i < 5_000; i++) {
            list1.remove(0);
        }
        endTime = System.nanoTime();

        System.out.printf("%s%,7d%s", "ArrayListからの削除\t",(endTime - startTime), "ナノ秒かかりました");

        System.out.println("");

        List<Product> list2 = new LinkedList<>(list1);

        startTime = System.nanoTime();
        for (int i = 0; i < 5_000; 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以外にも複数のオブジェクトを格納するための便利なクラスがたくさんあります。

オブジェクトの集合をコレクションといい、そのコレクションを操作するために用意されたJavaの標準APIをコレクションフレームワークといいます

この解説記事では、新人エンジニア研修の時間の関係でArrayListとLinkedListだけですが、

要素に重複を許したくないときに使うHashSet

要素を素早く取り出せる、また、キーワードを使って取り出せるHashMap

など、いろいろとあります。

このようにオブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークといいます。

ですので、ArrayListやLinkedListも当然コレクションフレームワークです。

実は、ListインターフェースのスーパーインターフェースにCollectionというインターフェースがあります。

コレクションフレームワーク
コレクションフレームワークの一部

また、機会があれば他のコレクションフレームワークについてもお話ししたいと思います。

しかし、ここでは、確実にArrayListをマスターしましょう。

他のコレクションフレームワークにも応用が利きますので。

Listインターフェースのメソッドをいろいろと試してみましょう。

そしてそのメソッドの内容をチーム内で共有しておいてください。

今回はコレクションフレームワークとジェネリクスについて見てきました。

次回のテーマは日付/時刻と列挙型です。

これまでお話しできなかったクラスについても説明しますね。

ということで、次回を楽しみに待っていてください。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ArrayListは要素の数にあわせて伸び縮みする配列で便利なメソッドを持っている  

□ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、様々な型に対応する汎用的なクラスやメソッドを設計できる機能である  

□型パラメータに格納できるのはオブジェクトだけである  

□オブジェクトの集合をまとめて操作するためのクラス群をコレクションフレームワークという

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題15.ArrayListとジェネリックス

目次1.名簿作成2.キーワード記憶アプリ3.テストの平均点4.カードシャッフルマシーン5.図形の面積で並び替えをする6.辞書のプロトタイプ7.オリジナル問題作成 1.名簿作成 …

【今回の復習Youtube】

068-コレクションフレームワーク-ArrayListの単純なサンプル

069-コレクションフレームワーク-ArrayListにオブジェクトを格納する

JavaSE8の解説に戻る

最後までお読みいただきありがとうございました。

新人エンジニア研修のプログラムを見てみる

当社は研修講師を募集中です(サブ講師のため未経験者可)