0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ArrayListを使うとき、意外と知らないままにしていたことを整理する

0
Posted at

この記事を書いた理由

ArrayList は日常的に使っているが、改めて「ちゃんとわかっているか」と問われると自信がなかった。

特に Arrays.asList() との組み合わせ、toArray() の使い方、List 型と ArrayList 型で宣言したときの違い。どれも「なんとなく動かしていた」レベルで止まっていたので、一度手を動かして確認した記録を残しておく。


ArrayListは動的に増減できる配列

通常の配列はサイズが固定で、宣言時に長さを決める必要がある。ArrayList はその制約がなく、要素の追加・削除に応じてサイズが変わる。

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();

        names.add("Steve");
        names.add("Alice");
        names.add("Bob");
        System.out.println(names); // [Steve, Alice, Bob]

        names.remove("Alice");
        System.out.println(names); // [Steve, Bob]

        names.set(0, "Smith");
        System.out.println(names); // [Smith, Bob]
    }
}

System.out.println(names) でそのままリストの中身が表示されるのは、AbstractCollection クラスが持つ toString() が呼ばれているから。素通りで表示できるので便利だが、なぜ表示されるかを把握していなかった時期があった。

型パラメータの <String> の部分はジェネリクスで、このリストが扱える型を指定している。右辺の <> はダイアモンド演算子といい、左辺で型が指定されていれば省略できる。


Arrays.asList()との組み合わせで詰まったこと

初期値ありでリストを作りたいとき、Arrays.asList() を使うことがある。ただ、これには落とし穴がある。

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

public class Main {
    public static void main(String[] args) {
        // Arrays.asList()だけでは要素の追加・削除ができない
        List<Integer> list = Arrays.asList(1, 2, 3);
        System.out.println(list.getClass().getName()); // java.util.Arrays$ArrayList
        // list.add(4); // UnsupportedOperationException が発生する

        // ArrayListでラップすることで追加・削除が可能になる
        List<Integer> listWrapped = new ArrayList<>(Arrays.asList(1, 2, 3));
        System.out.println(listWrapped.getClass().getName()); // java.util.ArrayList
        listWrapped.add(4);
        System.out.println(listWrapped); // [1, 2, 3, 4]
    }
}

Arrays.asList() が返すのは java.util.Arrays$ArrayList という内部クラスで、java.util.ArrayList とは別物だ。固定サイズのリストなので、要素の置換(set)はできても追加・削除はできない。

Arrays.asList() の戻り値をそのまま使い、後から add()remove() を呼ぶと UnsupportedOperationException が発生する。new ArrayList<>(Arrays.asList(...)) でラップするのを忘れないようにしたい。

以前、この違いを知らずに add() がなぜか動かないと首を傾げた記憶がある。クラス名を確認すれば一発でわかったのだが、そこに気づくまで少し時間を使った。


ArrayListを固定長配列に変換する

逆方向、ArrayList から通常の配列に変換したい場面もある。方法は2つある。

forループで1つずつコピーする方法(プリミティブ型向け)

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        int[] array = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i); // Integerからintへオートアンボクシングされる
        }

        for (int v : array) {
            System.out.println(v);
        }
    }
}

Integer から int への変換はオートアンボクシングによって自動で行われる。toArray() はプリミティブ型の配列には対応していないので、int[] が欲しい場合はこの方法になる。

toArray()を使う方法(オブジェクト型向け)

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("orange");

        String[] array = list.toArray(new String[list.size()]);
        for (String s : array) {
            System.out.println(s);
        }
    }
}

toArray() に変換先の型の配列インスタンスを渡す。指定したサイズが足りない場合は新しい配列が作られるが、list.size() を渡しておくのが無難だ。

余談だが、Java 11以降では toArray(String[]::new) というメソッド参照を使った書き方もできる。こちらのほうが簡潔に書けるので、プロジェクトのバージョンが対応していれば使ってみる価値がある。


List型とArrayList型、どちらで宣言すべきか

// ArrayList型で宣言
ArrayList<Integer> list1 = new ArrayList<>();

// List型で宣言
List<Integer> list2 = new ArrayList<>();

ArrayList 型で宣言すると ArrayList 固有のメソッドも使えるが、List 型で宣言しておくほうが柔軟性が高い。

具体的には、List<Integer> を引数に取るメソッドがあれば、ArrayList でも LinkedList でも Stack でも渡すことができる。実装クラスを後から変えたくなったとき、変更箇所は new している1行だけに収まる。

// LinkedListに切り替えたい場合、ここだけ変えればよい
List<Integer> list = new LinkedList<>();

ArrayList 型で宣言していると、渡せる先がそのクラスに縛られる。実際には ArrayList 固有のメソッドを直接使いたい場面はほとんどないので、迷ったら List 型で宣言しておくほうが保守しやすい。


整理して気づいたこと

Arrays.asList() の罠と、toArray() がプリミティブ型に使えない点は、それぞれ一度詰まって覚えた知識だった。今回改めて理由を確認して、「なぜそうなるか」まで言語化できた気がする。

List 型で宣言する意味については以前の記事でも触れたが、今回 ArrayList 固有のメソッドとの対比で改めて整理できた。ArrayList 固有のメソッドが必要なケースが実際にどれくらいあるか、もう少し意識して見てみようと思っている。


この記事を書いた人について

株式会社Flexibilityでエンジニアをしています。
DX推進・システム開発を軸に、エンジニアが自律的に動ける環境を大事にしている会社です。

技術的に面白いことをやっていきたい方や、働き方に柔軟さを求めている方は、
よかったら一度のぞいてみてください。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?