【Java】JavaのList入門:ArrayListとLinkedListの違いを初心者向けに解説

Java

こんにちは、かつコーチです。
今回は、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)は先頭から順にたどるため要素数が多いと遅くなる
操作ArrayListLinkedList
要素の取得(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することはできません。
必ずArrayListLinkedListなど、具体的な実装クラスを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, 初心者向け, コレクション

タイトルとURLをコピーしました