こんにちは、かつコーチです。
今回は、Javaでデータをまとめて扱うときによく使うList(複数の値を順番に並べて管理する仕組み)について解説します。
「配列と何が違うの?」「ArrayListとLinkedListってどっちを使えばいいの?」というつまずきポイントを、実際に手を動かしながら解消していきましょう。
Listとは?
配列との違い
配列は宣言時にサイズを決める必要があり、後から要素数を変えられません。
一方、Listは「要素の追加・削除が自由にできる、順序を持ったデータの集まり」を表すインターフェース(実装を持たない設計図のようなもの)です。
// 配列:サイズ固定
int[] array = new int[3];
// List:サイズ可変
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4); // 配列と違い自由に追加できる
なぜ2種類の実装があるのか
Listはインターフェースなので、実際に使うには実装クラスが必要です。
代表的なのがArrayList(内部で配列を使う実装)とLinkedList(内部で要素同士を鎖のようにつなぐ実装)です。
どちらも「順序付きで要素を管理する」という点は同じですが、内部構造が違うため得意な処理が異なります。
基本の書き方
ArrayListの基本操作
import java.util.ArrayList;
import java.util.List;
public class ArrayListSample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("りんご");
fruits.add("バナナ");
fruits.add("みかん");
System.out.println(fruits.get(1)); // バナナ
fruits.remove("バナナ");
System.out.println(fruits); // [りんご, みかん]
}
}
List<String>のように、宣言時はインターフェース型で受け取るのが基本です。
実装クラスを直接変数の型にしないことで、後からLinkedListに切り替えたくなったときも呼び出し側のコードを変えずに済みます。
LinkedListの基本操作
import java.util.LinkedList;
import java.util.List;
public class LinkedListSample {
public static void main(String[] args) {
List<String> queue = new LinkedList<>();
queue.add("Aさん");
queue.add("Bさん");
queue.add(0, "Cさん"); // 先頭に追加
System.out.println(queue); // [Cさん, Aさん, Bさん]
}
}
LinkedListはadd(0, ...)のような先頭・末尾への追加が得意です。
これは内部で要素同士を鎖状につないでいるため、先頭に挿入しても他の要素をずらす必要がないからです。
内部構造の違いが性能に影響する理由
- ArrayList:内部は配列。
get(index)で一発アクセスできるため、要素の取得が高速。ただし途中への挿入・削除は後ろの要素をずらす必要があり遅くなる - LinkedList:内部は前後のつながりを持つノード。先頭・末尾の追加削除は高速だが、
get(index)は先頭から順にたどるため要素数が多いと遅くなる
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 要素の取得(get) | 速い(O(1)) | 遅い(O(n)) |
| 末尾への追加 | 速い | 速い |
| 先頭・途中への挿入削除 | 遅い(ずらしが発生) | 速い |
| メモリ効率 | 良い | やや悪い(ノードの分だけ余分に使う) |
よくあるつまずきポイント・エラー対処
「List型で宣言したのにaddできない」のつまずき
実際に私が初心者に教えていたとき、次のようなコードでつまずいた人がいました。
// ❌Before:Listだけで生成しようとしてコンパイルエラー
List<String> names = new List<>(); // エラー:List is abstract; cannot be instantiated
names.add("かつコーチ");
実行すると error: List is abstract; cannot be instantiated というコンパイルエラーになります。Listはインターフェースなので、それ自体をnewすることはできません。
必ずArrayListやLinkedListなど、具体的な実装クラスをnewする必要があります。
// ✅After:実装クラスをnewする
List<String> names = new ArrayList<>();
names.add("かつコーチ");
System.out.println(names); // [かつコーチ]
この「インターフェース型で宣言し、実装クラスでnewする」という書き方は、Javaの多くの場面で登場する基本パターンなので、ここで覚えておくと後々楽になります。
応用・一歩先の使い方
どちらを選ぶべきかの判断軸
「とりあえずどちらを使えばいいか分からない」という人向けに、判断の目安をまとめます。
- 迷ったらArrayListを選ぶ(実務でも圧倒的にこちらの使用頻度が高い)
- 先頭への追加・削除が頻繁に発生する処理(キューやスタックのような使い方)ならLinkedListを検討する
- 要素数が数百万件を超えるなど、性能をシビアに気にする場面では、実際に処理内容に応じて計測してから選ぶ
Listを使うときによく組み合わせるメソッド
List<Integer> numbers = new ArrayList<>(List.of(3, 1, 4, 1, 5));
System.out.println(numbers.size()); // 5
System.out.println(numbers.contains(4)); // true
numbers.sort(null); // 昇順ソート
System.out.println(numbers); // [1, 1, 3, 4, 5]
List.of(...)はJava9以降で使える、変更不可のListを手軽に作る書き方です。
ここではその内容をコピーして変更可能なArrayListを作っています。
まとめ
この記事のポイント
- Listは要素の追加・削除が自由にできる順序付きコレクションのインターフェース
- ArrayListは取得が速く、実務での基本選択肢
- LinkedListは先頭・末尾の追加削除が速いが、取得は遅くなりがち
List型はインターフェースなので直接newできず、実装クラスをnewする必要がある
次に読むべき記事
- Setの基本:HashSetとTreeSetの違い
- Mapの基本:HashMap・TreeMap・LinkedHashMapの違い
タグ: Java, 初心者向け, コレクション