読み込み中...

Java List型完全ガイド!初心者でもマスターできる7つのステップ

Java List型の基本と応用を解説するイラストとテキスト Java
この記事は約34分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaで複数の値を順序付きで扱うなら、中心になる型はListです。ArrayListLinkedListList.ofArrays.asListの違いを押さえると、追加、取得、削除、並び替え、変換、例外対策まで見通しよく整理できます。

その理解が曖昧なままだと、IndexOutOfBoundsExceptionUnsupportedOperationException、意図しない参照共有、スレッド間の不整合でつまずきやすくなります。そのため、最小コードで動作の形を確認しながら、用途ごとに選ぶ実装を決めるのが現実的でしょう。

動作確認環境
  • Java 21 / OpenJDK 21
  • 標準ライブラリ: java.util パッケージ
📖 この記事で学べること
  • ListArrayListLinkedListの使い分け
  • addgetsetremoveの基本操作
  • Iterator、ラムダ式、Stream APIによる走査と加工
  • 配列、Set、コピー、比較への変換パターン
  • 範囲外アクセス、変更不可リスト、同期化の注意点

公式ドキュメントによれば、Java SE 21のListは順序を持つコレクションで、要素は整数インデックスで扱えます。Java SE 21のArrayListも、基本動作を確認する一次情報として参照できます。

Javaとは

Javaは、JVM上で動くオブジェクト指向言語です。コンパイルされた.classファイルは仮想マシンで実行されるため、サーバーアプリケーション、Android、業務システムなどで広く利用されますし、ここがポイントです。

その特徴は、classを中心にデータと処理をまとめ、packageで名前空間を分け、publicprivateでアクセス範囲を制御できる点です。一方、型が明確な言語なので、List<String>のようなジェネリクスを使うと、文字列だけを入れるリストとして扱えます。

基本的にJavaのコレクションはjava.utilにまとまっています。CollectionListSetMapを軸に考えると、順序、重複、キー検索という違いを整理しやすいです。

Javaの基礎文法を補強したい場合は、Javaのオブジェクト指向ガイドも役立ちますが、これは押さえたい点です。Listはオブジェクト指向の考え方と組み合わせて使う場面が多いため、interfaceと実装クラスの関係も合わせて見ると理解が進みますし、ここがポイントです。

List型とは

Listは順序を保持し、同じ値の重複も許すコレクションです。配列のString[]は長さが固定されますが、ArrayListは要素数に応じて内部配列を広げられます。

そのため、ユーザー入力、検索結果、CSVの行、APIレスポンスの一覧など、件数が実行時まで決まらないデータに向いているのが基本です。ただし、実装クラスによって得意な操作が変わるため、名前だけで選ばず処理内容から判断するとよい。

項目主なAPI用途注意点
生成new ArrayList<>()変更できるリスト型引数を合わせる
生成new LinkedList<>()先頭付近の追加削除ランダムアクセスは遅くなりやすい
生成List.of変更しない定数リストnull不可
追加add末尾または指定位置へ追加位置指定は既存要素が後ろへずれる
取得getインデックスで読む範囲外で例外
変更set既存要素の置換追加ではない
削除remove位置または値で削除Integerはオーバーロードに注意
件数sizeループ範囲の判断最後のインデックスはsize() - 1
空判定isEmpty要素がないか確認null参照とは別
検索contains値の有無を確認内部的にequalsが使われる
位置検索indexOf最初の位置を取得見つからない場合は-1
末尾検索lastIndexOf後ろ側の位置を取得重複値で使う
範囲subList一部のビューを扱う元リスト変更の影響に注意
走査for添字付き処理LinkedListでは非効率な場合
走査for-each全要素の読み取り添字不要の処理向き
走査Iterator安全な順次処理削除はiterator.removeを検討
加工replaceAll全要素を置換変更可能リストで使う
並び替えsort自然順や条件順に整列Comparatorを渡せる
変換stream抽出や集計終端操作が必要
収集collect結果をリスト化Java 16以降はtoListも候補
配列化toArray配列APIへ渡す型付き配列を渡す
配列からArrays.asList配列をリスト表示サイズ変更不可
コピーnew ArrayList<>(list)変更可能コピー浅いコピー
変更不可List.copyOf防御的コピー要素オブジェクト自体は別問題
重複排除new HashSet<>一意化順序が必要ならLinkedHashSet
同期化Collections.synchronizedList単純な同期複合操作は同期ブロックを検討
例外IndexOutOfBoundsException範囲外アクセス事前にsizeで確認
例外UnsupportedOperationException変更不可リスト変更生成方法を確認
比較equals順序と要素の一致同じ要素でも順序違いは不一致
削除条件removeIf条件一致を削除述語の副作用を避ける

具体的には、ArrayListは末尾追加と添字アクセスを多用する一覧に向きます。一方、LinkedListは構造上ノードをたどるため、単純なgetの多用には向かないと考えられる。

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

public class ListExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("りんご");
        fruits.add("ばなな");
        fruits.add("みかん");
        System.out.println(fruits.get(0));
    }
}

結果: 期待される出力は「りんご」です。get(0)は先頭要素を取得します。

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

public class NumbersList {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        for (int num : numbers) {
            System.out.println(num);
        }
    }
}

結果: 期待される出力は10、20、30の順です。for-eachはリストの順序を保って走査します。

💡 Tips: Listはインターフェースなので、変数の型はList<String>、生成はnew ArrayList<>()のように書くと、実装の差し替え余地を残せます。

Java List型の基本操作

Java List型の基本操作は、生成、追加、取得、変更、削除に分けると整理しやすいです。初心者がつまずきやすいのは、インデックスが0から始まる点と、remove(1)remove(Integer.valueOf(1))の意味が変わる点になるのが目安です。

その違いを理解するには、短いコードでsizeと中身を出すのが近道です。Javaの制御構文やクラスの考え方に不安があれば、Javaのオーバーライド解説でメソッドの振る舞いも確認できます。

Listインスタンスの生成

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

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        System.out.println(list.size());
    }
}

結果: 期待される出力は「0」です。ArrayListは生成直後なら空のリストになります。

import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        System.out.println(list.isEmpty());
    }
}

結果: 期待される出力は「true」です。LinkedListListとして扱えます。

要素の追加と取得

要素を増やす処理ではadd、読む処理ではgetを使いるのがポイントです。このとき、add(value)は末尾追加、add(index, value)は指定位置への挿入という違いがあるのが基本です。

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

public class AddElementSample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("ばなな");
        list.add("みかん");
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

結果: 期待される出力は「りんご」「ばなな」「みかん」の順です。addした順序が保持されます。

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

public class GetElementSample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("ばなな");
        list.add("みかん");
        String secondFruit = list.get(1);
        System.out.println("2番目のフルーツは: " + secondFruit);
    }
}

結果: 期待される出力は「2番目のフルーツは: ばなな」です。get(1)は人間の数え方では2番目にあたります。

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

public class InsertSample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("C");
        list.add(1, "B");
        System.out.println(list);
    }
}

結果: 期待される出力は「[A, B, C]」です。add(1, "B")により、既存のCは後ろへ移動します。

要素の削除と変更

要素を取り除くときはremove、置き換えるときはsetを使います。ただし、setは既存位置の更新なので、空の位置へ値を入れる用途には使えません。

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

public class RemoveExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("みかん");
        list.add("ばなな");
        list.remove(1);
        System.out.println(list);
    }
}

結果: 期待される出力は「[りんご, ばなな]」です。remove(1)によりインデックス1の要素が削除されます。

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

public class SetExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("みかん");
        list.add("ばなな");
        list.set(1, "グレープフルーツ");
        System.out.println(list);
    }
}

結果: 期待される出力は「[りんご, グレープフルーツ, ばなな]」です。setは要素数を変えずに値だけを置き換えます。

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

public class RemoveIntegerExample {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.remove(Integer.valueOf(2));
        System.out.println(nums);
    }
}

結果: 期待される出力は「[1, 3]」です。Integer.valueOf(2)を渡すと、インデックスではなく値の2を削除します。

⚠️ 注意: List<Integer>remove(2)と書くと、値の2ではなくインデックス2の削除として解釈されます。値を消すならInteger.valueOfを使うと明確です。

サンプルコードとその解説

サンプルコードでは、基本操作からラムダ式、Stream、並び替え、配列変換、コピー、比較までをつなげて確認するのが一般的です。実装パターンとしてよく見るのは、Listで受け取り、必要に応じてArrayListへコピーしてから変更する形です。

これらの処理はWebアプリケーションの一覧表示や入力値の整形でもよく使われますが、これは押さえたい点です。注釈や設定値の扱いを合わせて学ぶなら、Javaアノテーションのガイドも関連します。

サンプルコード1:Listの基本操作

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

public class ListBasicOperations {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Cherry");
        String element = myList.get(1);
        System.out.println("取得した要素: " + element);
        myList.add(1, "Orange");
        System.out.println("リストの内容: " + myList);
        myList.remove(2);
        System.out.println("リストの内容: " + myList);
    }
}

結果: 期待される出力は「取得した要素: Banana」、続いて「[Apple, Orange, Banana, Cherry]」、削除後に「[Apple, Orange, Cherry]」です。

サンプルコード2:Iteratorを利用した操作

import java.util.ArrayList;
import java.util.Iterator;

public class ListIteratorExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("リンゴ");
        list.add("バナナ");
        list.add("オレンジ");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println(fruit);
        }
    }
}

結果: 期待される出力は「リンゴ」「バナナ」「オレンジ」の順です。hasNextで次の要素を確認し、nextで値を取り出します。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorRemoveExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            if (it.next().equals("B")) {
                it.remove();
            }
        }
        System.out.println(list);
    }
}

結果: 期待される出力は「[A, C]」です。走査中の削除ではIterator.removeを使うと意図が明確になります。

サンプルコード3:ラムダ式を利用した操作

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

public class LambdaListOperation {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        list.replaceAll(item -> item.toUpperCase());
        list.forEach(item -> System.out.println(item));
    }
}

結果: 期待される出力は「APPLE」「BANANA」「CHERRY」です。replaceAllが各要素を大文字へ置き換えます。

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

public class RemoveIfExample {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.removeIf(n -> n % 2 == 0);
        System.out.println(nums);
    }
}

結果: 期待される出力は「[1, 3]」です。removeIfに渡した条件に合う偶数が取り除かれます。

サンプルコード4:Stream APIを利用した操作

Stream APIは、リストを直接変更せずに変換や抽出を表現しやすい書き方です。一方、読みやすさは処理の長さに左右されるため、単純な全件表示ならfor-eachのほうが扱いやすい場合もあります。

import java.util.List;

public class StreamStartExample {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        System.out.println(list);
    }
}

結果: 期待される出力は「[1, 2, 3, 4, 5]」です。List.ofは変更不可のリストを作りますし、これが一つの目安です。

import java.util.List;
import java.util.stream.Collectors;

public class StreamMapExample {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        List<Integer> doubledList = list.stream()
                .map(n -> n * 2)
                .collect(Collectors.toList());
        System.out.println(doubledList);
    }
}

結果: 期待される出力は「[2, 4, 6, 8, 10]」です。mapは各要素を変換します。

import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterExample {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5, 6);
        List<Integer> evenList = list.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());
        System.out.println(evenList);
    }
}

結果: 期待される出力は「[2, 4, 6]」です。filterは条件に合う要素だけを残します。

import java.util.List;

public class StreamReduceExample {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        int sum = list.stream().reduce(0, Integer::sum);
        System.out.println(sum);
    }
}

結果: 期待される出力は「15」です。reduceは要素を畳み込んで単一の値にします。

サンプルコード5:Listの並び替え

import java.util.ArrayList;
import java.util.Collections;

public class ListSortExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Banana");
        list.add("Apple");
        list.add("Cherry");
        Collections.sort(list);
        System.out.println(list);
    }
}

結果: 期待される出力は「[Apple, Banana, Cherry]」です。Collections.sortは自然順で並び替えます。

import java.util.ArrayList;
import java.util.Comparator;

public class ListSortExample2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Banana");
        list.add("Apple");
        list.add("Cherry");
        list.sort(Comparator.reverseOrder());
        System.out.println(list);
    }
}

結果: 期待される出力は「[Cherry, Banana, Apple]」です。Comparator.reverseOrderにより降順になります。

サンプルコード6:Listと配列の相互変換

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

public class ListToArray {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("ばなな");
        list.add("みかん");
        String[] array = list.toArray(new String[0]);
        for (String item : array) {
            System.out.println(item);
        }
    }
}

結果: 期待される出力は「りんご」「ばなな」「みかん」の順です。toArray(new String[0])で型付き配列へ変換できます。

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

public class ArrayToList {
    public static void main(String[] args) {
        String[] array = {"りんご", "ばなな", "みかん"};
        List<String> list = Arrays.asList(array);
        System.out.println(list);
    }
}

結果: 期待される出力は「[りんご, ばなな, みかん]」です。Arrays.asListで配列をリストとして扱えます。

サンプルコード7:Listのコピーと比較

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

public class ListCopyExample {
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>();
        originalList.add("Apple");
        originalList.add("Banana");
        originalList.add("Cherry");
        List<String> copiedList = new ArrayList<>(originalList);
        copiedList.add("Date");
        System.out.println("Original List: " + originalList);
        System.out.println("Copied List: " + copiedList);
    }
}

結果: 期待される出力は元のリストが「[Apple, Banana, Cherry]」、コピー側が「[Apple, Banana, Cherry, Date]」です。

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

public class ListComparisonExample {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("Apple");
        list1.add("Banana");
        List<String> list2 = new ArrayList<>();
        list2.add("Apple");
        list2.add("Banana");
        boolean isEqual = list1.equals(list2);
        System.out.println(isEqual);
    }
}

結果: 期待される出力は「true」です。equalsは要素と順序が一致する場合にtrueを返します。

Java List型の応用

Java List型の応用では、部分リスト、重複排除、変更不可リスト、条件削除などを組み合わせます。一般的に、読み取り用のリストと更新用のリストを分けると、予期しない変更を抑えやすいです。

その考え方は、日付判定や入力チェックのような処理にも通じますが、覚えておくと役立つでしょう。条件分岐の書き方を補いたい場合は、Javaでうるう年を判定する解説が参考になるのが目安です。

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

public class ListAdvancedExample1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("バナナ");
        list.add("ぶどう");
        list.add("みかん");
        list.add("キウイ");
        List<String> sublist = list.subList(2, 4);
        System.out.println(sublist);
    }
}

結果: 期待される出力は「[ぶどう, みかん]」です。subList(2, 4)は開始位置を含み、終了位置を含みません。

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ListAdvancedExample2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("りんご");
        list.add("バナナ");
        list.add("ぶどう");
        list.add("みかん");
        list.add("りんご");
        Set<String> set = new HashSet<>(list);
        System.out.println(set);
    }
}

結果: 期待される出力は重複しない果物名の集合です。HashSetは順序を保証しないため、表示順は固定と考えないほうがよい。

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class UniqueKeepOrderExample {
    public static void main(String[] args) {
        List<String> list = List.of("A", "B", "A", "C");
        Set<String> set = new LinkedHashSet<>(list);
        System.out.println(set);
    }
}

結果: 期待される出力は「[A, B, C]」です。LinkedHashSetを使うと、初出順を保ちながら重複を排除できます。

import java.util.List;

public class CopyOfExample {
    public static void main(String[] args) {
        List<String> source = List.of("A", "B");
        List<String> copied = List.copyOf(source);
        System.out.println(copied.contains("A"));
    }
}

結果: 期待される出力は「true」です。List.copyOfは変更不可リストを作る用途に向きます。

ℹ️ 補足: List.ofList.copyOfで作ったリストにaddsetを行うと、UnsupportedOperationExceptionが発生するのが現実的です。更新する予定があるならnew ArrayList<>(source)で変更可能なコピーを作りますし、これが一つの目安です。

注意点と対処法

注意点として特に押さえたいのは、範囲外アクセス、変更不可リスト、マルチスレッドでの共有です。これらはコンパイル時に見つからない場合があり、実行時例外として表面化します。

そのため、sizeで範囲を確認し、変更可能性を生成時に決め、共有リストの複合操作では同期を検討すると整理できます。文字列の扱いも合わせて整理するなら、Javaエスケープ処理の解説も確認するとよいでしょう。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SynchronizedListExample {
    public static void main(String[] args) {
        List<Integer> list = Collections.synchronizedList(new ArrayList<>());
        list.add(1);
        list.add(2);
        synchronized (list) {
            for (Integer value : list) {
                System.out.println(value);
            }
        }
    }
}

結果: 期待される出力は「1」「2」の順です。Collections.synchronizedListを使っても、反復処理では同期ブロックを組み合わせるのが一般的です。

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

public class IndexOutOfBoundsHandling {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        for (int i = 0; i < list.size() + 1; i++) {
            if (i < list.size()) {
                System.out.println(list.get(i));
            } else {
                System.out.println("インデックスが範囲外です");
            }
        }
    }
}

結果: 期待される出力は1から5までの数値と、末尾に「インデックスが範囲外です」です。i < list.size()で範囲外アクセスを避けます。

import java.util.List;

public class UnsupportedExample {
    public static void main(String[] args) {
        List<String> fixed = List.of("A", "B");
        System.out.println(fixed.size());
    }
}

結果: 期待される出力は「2」です。このリストは読み取りには使えますが、addなどの変更操作には向きません。

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

public class NullHandlingExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add(null);
        list.add("A");
        System.out.println(list.contains(null));
    }
}

結果: 期待される出力は「true」です。ArrayListnullを保持できますが、List.ofではnullを受け付けません。

⚠️ 注意: subListは独立した完全コピーではなく、元リストと関係を持つビューです。安全に保存したい場合はnew ArrayList<>(list.subList(...))でコピーします。

まとめ

JavaのListは、順序付きの複数データを扱うための中心的なインターフェースです。変更可能な一覧にはArrayList、先頭や末尾の操作を意識する場面ではLinkedList、変更させたくない定数的な一覧にはList.ofList.copyOfを使い分けます。

基本操作はaddgetsetremovesizeに集約できると理解できるのがポイントです。その上で、IteratorforEachstreamfiltermapcollectを覚えると、読み取り、変換、抽出のコードが組み立てやすくなります。

ただし、インデックスは0始まりであり、範囲外ならIndexOutOfBoundsExceptionになります。変更不可リストに更新操作を行えばUnsupportedOperationExceptionが発生するため、生成方法と用途を合わせることが安定した実装につながる。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。