本ページはプロモーションが含まれています

Java入門

【初心者向け】JavaのList(リスト)とは?使い方から4つの種類まで徹底解説

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

「Javaの勉強を始めたけど、配列とListの違いがよくわからない…」

「ArrayListとLinkedList、どちらを使えばいいの?」

Javaを学び始めると、誰もが一度はjavaのlistという壁にぶつかります。私自身、駆け出しのころはListの使い分けに悩み、パフォーマンスの悪いコードを書いてしまった経験があります。

この記事では、そんな過去の私のような「javaのlistについて知りたい、使いこなしたい」と考えているあなたに向けて書きました。

この記事を読めば、以下の悩みを解決できます。

  • JavaのListの基本概念がわかる
  • ArrayListLinkedListなど、状況に応じたListの選び方が身につく
  • コード例を通じて、Listの基本的な使い方から応用テクニックまで学べる

基礎からしっかり解説するので、ぜひ最後まで読んでみてください。

JavaのListとは

JavaのListは、複数のデータ(要素)を順序付けて格納するための入れ物です。公式には「コレクションフレームワーク」という、データを扱うための仕組みの一部として提供されています。

Listの基本概念と特徴

Listインターフェースは、データを扱う上で非常に便利な3つの大きな特徴を持っています。

ポイント

  1. 順序の保持: 追加した要素が、追加した順番通りに格納されます。0番目、1番目、2番目…というように、各要素にインデックスが割り振られるのが特徴です。
  2. 重複の許可: 同じ値を持つ要素を複数格納できます。例えば、"apple"という文字列を3つListに入れることも可能です。
  3. 動的なサイズ変更: 配列とは違い、格納する要素の数に応じてサイズを自動で変更してくれます。最初に「要素は10個まで」と決める必要はありません。

これらの特徴により、プログラムの実行中にデータ数が変動するような場面で、javaのlistは非常に役立ちます。

Listのメリット・デメリット

便利なlistにも、メリットとデメリットがあります。

メリット

  • 要素数の変更が容易: アプリケーションの実行中にデータが増減しても柔軟に対応可能です。
  • 豊富なメソッド: 要素の追加、削除、検索、並び替えなど、データを操作するための便利なメソッドが多数用意されています。

デメリット

  • プリミティブ型を直接格納できない: intdoubleなどのプリミティブ型は直接格納できません。IntegerDoubleといったラッパークラスに変換して格納する必要があります。
  • メモリ消費: 配列に比べて、付加的な情報(サイズなど)を保持するため、わずかに多くのメモリを消費します。

これらの特性を理解し、場面に応じて配列と使い分けるのが重要です。

Listの種類

JavaのListはインターフェース(設計図)であり、実際に使う際にはそれを実装した具象クラスのインスタンスを生成します。ここでは代表的な4つのクラスを紹介します。

ArrayListとは

ArrayListは、javaのlistの中で最もよく使われるクラスの一つです。内部的には配列を利用してデータを管理しています。

特徴

  • ランダムアクセスが非常に高速: インデックスを指定して要素を取得する操作(get()メソッド)が速いです。内部が配列なので、インデックスからメモリアドレスを直接計算できるためです。
  • 要素の追加・削除は遅くなる場合がある: リストの末尾以外に要素を追加・削除すると、後続の要素をすべてずらす処理が発生するため、パフォーマンスが低下することがあります。

日常的な開発では、まずArrayListを検討すると良いでしょう。データの参照が主な用途の場合に特に強みを発揮するlistです。

LinkedListとは

LinkedListは、各要素が前後の要素への参照(リンク)を持つことで、リスト構造を実現しています。データが数珠つなぎになっているイメージです。

特徴

  • 要素の追加・削除が高速: 特定の位置に要素を追加・削除する際、前後の要素のリンクを変更するだけで済むため、処理が高速です。ArrayListのように後続の要素をずらす必要がありません。
  • ランダムアクセスが遅い: インデックスを指定して要素を取得する場合、先頭から順番に要素をたどっていく必要があります。そのため、リストの要素数が多いほど時間がかかります。

データの追加や削除が頻繁に発生する処理には、LinkedListが適しています。

VectorとStackの違い

VectorStackは、古いバージョンのJavaから存在するクラスです。

VectorArrayListと非常によく似ていますが、スレッドセーフであるという大きな違いがあります。つまり、複数のスレッドから同時にアクセスしても、データが壊れないように設計されています。

ただし、その分パフォーマンスはArrayListに劣るため、現在ではCollections.synchronizedList()など他の方法でスレッドセーフを実現することが一般的です。

StackVectorを継承したクラスで、LIFO(Last-In, First-Out)、つまり「後入れ先出し」のデータ構造を実現します。本を積み上げて、上から取っていくイメージです。push()で要素を追加し、pop()で要素を取り出します。現在では、Stackの代わりにArrayDequeを使用することが推奨されています。

どのListを使うべきかの選び方

では、どのlistを選べばよいのでしょうか。選択の基準はシンプルです。

  • 要素の参照(取得)がメインで、追加・削除が少ない場合: ArrayList を選びましょう。ほとんどのケースでこれが最適解です。
  • リストの先頭や中間で、要素の追加・削除が頻繁に発生する場合: LinkedList が適しています。
  • マルチスレッド環境で、スレッドセーフなListが必要な場合: ArrayListCollections.synchronizedList()でラップするか、CopyOnWriteArrayListを検討します。
  • スタック構造が必要な場合: ArrayDeque を使用するのが現在のベストプラクティスです。

まずはArrayListを基本と考え、特定の要件がある場合に他のクラスを検討するという流れで問題ありません。

Listの使い方

ここからは、具体的なコードを見ながらJavaのListの使い方を学んでいきましょう。今回はArrayListを例に説明します。

Listの作成方法

Listを生成する際は、Listインターフェースの変数に、ArrayListなどの具象クラスのインスタンスを代入するのが一般的です。

// String型の要素を格納するArrayListを作成
List<String> fruits = new ArrayList<>();

// Integer型の要素を格納するLinkedListを作成
List<Integer> numbers = new LinkedList<>();

このようにインターフェース型で宣言しておくと、後からArrayListLinkedListに変更したくなった場合でも、修正箇所を最小限に抑えられます。

Listの要素の追加・削除・取得

Listの基本的な操作は、以下のメソッドで行います。

  • 追加: add()
  • 取得: get()
  • 更新: set()
  • 削除: remove()
  • 要素数取得: size()
  • 空かどうかの判定: isEmpty()
// Listの作成
List<String> languages = new ArrayList<>();

// 要素の追加 (add)
languages.add("Java");
languages.add("Python");
languages.add("Go");
System.out.println("追加後: " + languages); // 出力: [Java, Python, Go]

// 2番目の要素を取得 (get) ※インデックスは0から始まる
String lang = languages.get(1);
System.out.println("取得した要素: " + lang); // 出力: Python

// 2番目の要素を更新 (set)
languages.set(1, "Ruby");
System.out.println("更新後: " + languages); // 出力: [Java, Ruby, Go]

// 3番目の要素を削除 (remove)
languages.remove(2);
System.out.println("削除後: " + languages); // 出力: [Java, Ruby]

// 要素数を取得 (size)
int count = languages.size();
System.out.println("現在の要素数: " + count); // 出力: 2

イテレーションの方法(for文・for-each・Iterator)

Listに格納されたすべての要素を順番に処理するには、繰り返し処理(イテレーション)を使います。主に3つの方法があります。

拡張for文 (for-each)

最もシンプルで推奨される方法です。

List<String> animals = new ArrayList<>();
animals.add("dog");
animals.add("cat");
animals.add("rabbit");

for (String animal : animals) {
    System.out.println(animal);
}

従来のfor文

インデックスを使って要素にアクセスします。

for (int i = 0; i < animals.size(); i++) {
    System.out.println(animals.get(i));
}

Iterator

ループの最中に要素を安全に削除したい場合に使用します。

Iterator<String> iterator = animals.iterator();
while (iterator.hasNext()) {
    String animal = iterator.next();
    if (animal.equals("cat")) {
        iterator.remove(); // 安全な削除方法
    }
}
System.out.println(animals); // 出力: [dog, rabbit]

Listの並び替えと検索(Collectionsクラス活用)

Collectionsというユーティリティクラスを使うと、Listの並び替えや検索が簡単に行えます。

List<Integer> scores = new ArrayList<>();
scores.add(80);
scores.add(95);
scores.add(72);

// 昇順に並び替え (sort)
Collections.sort(scores);
System.out.println("昇順ソート後: " + scores); // 出力: [72, 80, 95]

// 降順に並び替え
Collections.sort(scores, Collections.reverseOrder());
System.out.println("降順ソート後: " + scores); // 出力: [95, 80, 72]

// バイナリサーチで要素を検索 (事前にソートが必要)
Collections.sort(scores); // まず昇順にソート
int index = Collections.binarySearch(scores, 80);
System.out.println("80のインデックス: " + index); // 出力: 1

Listの応用テクニック

基本的な使い方をマスターしたら、次はより高度なテクニックを見ていきましょう。

Listのコピーとサブリストの作成

Listのコピー

元のListとは別に、同じ要素を持つ新しいListを作成したい場合があります。

List<String> original = new ArrayList<>();
original.add("A");
original.add("B");

// コンストラクタを使ってコピー(シャローコピー)
List<String> copy = new ArrayList<>(original);
copy.add("C");

System.out.println("オリジナル: " + original); // 出力: [A, B]
System.out.println("コピー: " + copy);      // 出力: [A, B, C]

サブリストの作成

subList()メソッドを使うと、Listの一部を切り出して新しいListとして扱えます。

List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
    numbers.add(i); // [1, 2, 3, 4, 5]
}

// インデックス1から3までを切り出す (3は含まない)
List<Integer> sub = numbers.subList(1, 3);
System.out.println("サブリスト: " + sub); // 出力: [2, 3]

// 注意: サブリストへの変更は元のリストにも反映される
sub.set(0, 99);
System.out.println("変更後のサブリスト: " + sub);     // 出力: [99, 3]
System.out.println("変更後のオリジナル: " + numbers); // 出力: [1, 99, 3, 4, 5]

subList()は元のリストの「ビュー」を返すため、取り扱いには注意が必要です。

ListとStream APIの活用

Java 8で導入されたStream APIを使うと、Listの集計やフィルタリング処理を非常に簡潔に記述できます。

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 偶数だけをフィルタリングして、2倍にして、新しいListを作成する
List<Integer> result = numbers.stream()      // ストリームを生成
                              .filter(n -> n % 2 == 0) // 偶数のみを抽出
                              .map(n -> n * 2)         // 各要素を2倍にする
                              .collect(Collectors.toList()); // 結果をListに変換

System.out.println(result); // 出力: [4, 8, 12, 16, 20]

for文で書くよりも、何をしているかが一目でわかりやすいのが特徴です。

不変List(Immutable List)の作り方

一度作成したら要素を変更・追加・削除されたくない、読み取り専用のListを作りたい場合があります。これを「不変List」と呼びます。

Java 9以降: List.of()

// 要素を変更できないListを作成
List<String> immutableList = List.of("Java", "Python", "Go");

// immutableList.add("Ruby"); // UnsupportedOperationExceptionが発生する

Java 8以前: Collections.unmodifiableList()

List<String> modifiableList = new ArrayList<>();
modifiableList.add("Java");
modifiableList.add("Python");

// 変更不可能なビューを作成
List<String> unmodifiableList = Collections.unmodifiableList(modifiableList);

// unmodifiableList.add("Go"); // UnsupportedOperationExceptionが発生する

不変Listは、データを安全に保ちたい場合に非常に有効なテクニックです。

Listを使う際の注意点

最後に、javaのlistを使う上で注意すべき点を3つ解説します。

スレッド安全性の問題

ArrayListLinkedListは、スレッドセーフではありません。複数のスレッドから同時に同じListインスタンスにアクセスして変更を加えると、予期せぬエラー(ConcurrentModificationExceptionなど)やデータの不整合が発生する可能性があります。

マルチスレッド環境でListを扱う場合は、以下のいずれかの方法を検討する必要があります。

  • Collections.synchronizedList()でListをラップする
  • java.util.concurrentパッケージのCopyOnWriteArrayListクラスを使用する

パフォーマンスに影響する操作

Listの種類によって、得意な操作と苦手な操作があります。

  • ArrayList: リストの先頭や中間への要素の追加・削除は、後続の全要素をシフトさせるため、要素数が多いほどパフォーマンスが著しく低下します。
  • LinkedList: インデックスを指定した要素の取得(get(index))は、先頭から順にたどるため、要素数が多いほど時間がかかります。

ループの中でこれらの操作を多用すると、アプリケーション全体の性能に影響を与える可能性があるため、注意が必要です。

Null要素や重複の取り扱い

Listはnullや重複した要素を許容します。これは柔軟である一方、意図しないnullが紛れ込むとNullPointerExceptionの原因になります。また、重複を許したくないデータを扱う場合は、Setインターフェースを使うか、add()する前にcontains()メソッドで存在チェックを行うなどの工夫が求められます。

まとめ

この記事では、JavaのListについて、基本的な概念から種類、具体的な使い方、応用テクニック、注意点までを網羅的に解説しました。

  • Listは順序を保持し、重複を許す動的なデータ構造
  • 普段使いはArrayList、追加・削除が多いならLinkedListを選ぶ
  • 基本的な操作はadd, get, remove, setで行う
  • Stream APIや不変Listなどの応用テクニックも活用しよう

JavaのListは、あらゆる開発で登場する基本中の基本です。しかし、その特性をきちんと理解しているかどうかで、書けるコードの品質は大きく変わります。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発IT企業勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門