LoginSignup
9
14

More than 5 years have passed since last update.

ArrayListとListから見ゆるインタフェースの役割

Last updated at Posted at 2017-06-25

とあるWeb企業で新卒でエンジニアをしています。
主に日頃はAndroid開発をしています。

そもそも事の発端

先日、メンターさんに以下のJavaコード(Android)について突っ込みを受けました。

ArrayList<Hoge> hoges = new ArrayList<>();

メンターさん「なんでhogesはArrayListで宣言しているの?Listにしないのはなんで?」

こう尋ねられたとき、私は答えることができませんでした。

Hogeのインスタンスを複数格納してあげるのにはArrayListが必要で、そのArrayListを入れる変数はArrayListで宣言されるのは当たり前ではないのかと。。。

しかし、その後説明を受け自分なりに納得したため、自分の理解のためにまとめたいと思います。

ListとArrayList

まず、ListとArrayListの関係についてまとめます。

List

Listはインタフェースです。インタフェースはクラスがどういうメソッドを持つか、という仕様がまとまったもので、Listはコレクションを実現するために、どういったメソッドが必要となるのか定めている仕様です。

ソースの中身を見ると、(一部のメソッドだけ抜粋)

public interface List<E> extends Collection<E> {
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    boolean add(E e);
    boolean remove(Object o);
    boolean equals(Object o);
        ・
        ・
        ・
            etc...

というように、ロジックの書かれていないメソッドがまとめられています。

ArrayList

ArrayListはソースを抜粋すると、(上記のListのメソッドに対応するもののみ抜粋)

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
       public int size() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
            return this.size;
       }

       public boolean isEmpty() {
            return size == 0;
       }

       public boolean contains(Object o) {
            return indexOf(o) >= 0;
       }

       public Iterator<E> iterator() {
            return listIterator();
       }

       public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
       }

       public boolean remove(Object o) {
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
                }
            }
            return false;
       }

       public boolean contains(Object o) {
            return indexOf(o) >= 0;
       }

                   ・
                   ・
                   ・
                   etc...

というように、Listインターフェイスを実装したクラスで、Listでは中身のなかったメソッドに処理が記述されているのがわかります。
また、implementsの後を見てわかる通り、ArrayListはListだけでなくRandomAccess, Cloneable, java.io.Serializableを実装しているため他にも様々なメソッドを持っています。

ちなみにListを実装しているクラスはArrayListの他に Stack, Vector, LinkedListなどたくさんあります。
ArrayListはListで定められたメソッドを実現するクラスのうちの一つです。

インタフェースって何が嬉しいの?

話がやや脱線しますが、メソッドの仕様がまとめられていて、実装クラス側で実際の処理を実装する事の利点はなんなのでしょうか?

それは、クラスの呼び出し側が実装の中身を意識せずに済む点です。
例えば、次のインタフェースがあったとします。

public interface FileReader {
    void open();
    void close();
    void read();
}

このFileReaderを実装したのが、CSVFileReaderとJSONFileReaderだとします。

public class CSVFileReader {

    public void open() {
        csvをよしなにオープンする処理
    }

    public void close() {
        csvをよしなにクローズしてくれる処理
    }

    public void read() {
        csvをよしなに読んでくれる処理
    }

}
public class JSONFileReader {

    public void open() {
        JSONをよしなにオープンする処理
    }

    public void close() {
        JSONをよしなにクローズしてくれる処理
    }

    public void read() {
        JSONをよしなに読んでくれる処理
    }

}

これらのクラスを引数として受け取り、ファイルの文字数をカウントするメソッドがあったとします。

public int getCount(FileReader fileReader) {
    int count = 0;
    while(true) {
        fileReader.read();
        //終了条件になったらループを抜けるロジックがこの辺にあるとする
        count++;
    }
    return count; 
}

インタフェースはメソッドの定義だけしてあり、そのままではインスタンス化することはできません。
しかし、型として宣言するとこはできます。

つまり、read()の実装内容がなんであろうと、read()という呼び出し口さえわかっていればFileReaderを実装したクラスを使うことができます。
読む内容がCSVなのか、JSONなのか呼び出し側が知っている必要はありません。

このように、呼び出す側のロジックを一本化する、つまり共通のメインルーチンを作る仕組みをポリモーフィズムと言います。

なぜその型で宣言するのか?

ListとArrayListの関係についてまとめたところで

先ほどの

ArrayList<Hoge> hoges = new ArrayList<>();

という書き方について考えます。

ポリモーフィズムの考え方にのっとれば、

List<Hoge> hoges = new ArrayList<>();

と、書くことができます。

ArrayListのメソッド

boolean add(E e);
boolean remove(Object o);
boolean equals(Object o);

などはインタフェースとしてListに定義され、それを実装しています。
例えば、

List<Hoge> hoges = new LinkedList<>();

とLinkedListのインスタンスをhogesに格納した場合でも

hoges.add(hoge);

のように同じメソッドを呼び出せます。
先ほどの、インタフェースの利点でも説明したように、
Listを引数に取るメソッドがあった場合、ArrayListでもLinkedListでも、Stackでも受け取れるため、柔軟性が高まります。

もちろん、

ArrayList<Hoge> hoges = new ArrayList<>();

と書くことが全面的にダメだということではありません。
もちろんArrayListはRandomAccess, Cloneable, java.io.Serializableも実装しており、それらで定義されているメソッドを使う必要があるのであればArrayListで宣言する必要があります。

そうでなければ

List<Hoge> hoges = new ArrayList<>();

という書き方がいいでしょう。

まとめ

ListとArrayListで検索すると、

List<Hoge> hoges = new ArrayList<>();

の方がいい、という記事が見つかりますが、
ここで大事なのは丸暗記ではなく、なぜその宣言をするのか、という理由がちゃんと説明できることだと思います。

なので、今回はListとArrayListを例として、なぜそうするのかという理由を自分なりにまとめました。

まだまだJava勉強中の身でおかしな点がたくさんあるかと思います。
ツッコミ、ご意見お待ちしています。

参考

3分でわかるJavaの基礎】ListとArrayListの違いをまとめてみた
http://www.sejuku.net/blog/14886

Java、ListとArrayListの違い。
http://piyopiyocs.blog115.fc2.com/blog-entry-219.html

List(JavaPlatform SE 7)
http://docs.oracle.com/javase/jp/7/api/java/util/List.html

インターフェースとは?~継承とは役割が違う~|オブジェクト指向プログラミング(OOP)をおさらいしよう(3)
https://www.gixo.jp/blog/5159/

平澤 章 オブジェクト指向でなぜつくるのか 第2版 日経BP社

9
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
14