🐈

[Javaの基礎]Listについて

に公開

はじめに

ここでは以下についてまとめています。

  • Listについて
  • ArrayListLinkedListの違いについて
  • ArrayListの使用方法
  • Listの主なメソッド
  • Listでのループ処理
  • Stream API

Listとは

Listとは要素を順序付きで格納できるコレクションで、配列よりも柔軟にデータを扱えるのが特徴です。サイズは可変で、データの追加・削除・検索が簡単に行えます。また、要素の重複を許可しておりインデックスでアクセスが可能になってます。
コレクションは参照型しか格納できません。ArrayList<int>のようにプリミティブ型はコレクションに直接格納できなくなっているのでラッパークラス(Integerなど)を使用します。
java.util.Listにはインターフェースで以下のような実装クラスがあります。

  • 主な実装クラス
    • ArrayList(もっとも一般的)
    • LinkedList
    • Vector(非推奨気味)

ArrayList vs LinkedList

以下の表のようにArrayListLinkedListはどちらもListインターフェースを実装するクラスですが、内部構造がまったく異なるため、性能特性にも大きな違いがあります。

特徴 ArrayList LinkedList
末尾へ要素の追加 高速 やや遅め
挿入・削除 遅い(要素への移動が必要) 高速(ポインタ操作)
ランダムアクセス 高速(配列ベース) 遅い(リストをたどる必要)
  1. 末尾への追加
  • ArrayList:高速
    • 内部は可変長配列で管理。
    • 末尾への追加は、空きがあれば O(1) の時間で実行可能。
    • ただし、配列容量を超えると拡張(再コピー)が発生し、その際に時間がかかる(O(n))。
  • LinkedList:やや遅い
    • 内部は双方向リンク構造。
    • 末尾のノードをたどって追加するが、ポインタの更新が必要なのでArrayListよりわずかに遅いことがある。
    • ただし、頻繁な挿入・削除がある場合は有利。
  1. 挿入・削除(中間の位置)
  • ArrayList:遅い
    • 挿入や削除後、後ろの要素をすべて1つずらす必要があるため、O(n)のコスト。
  • LinkedList:高速
    • リスト内のノードを見つけた後、前後のノードのポインタを変更するだけで済む。
    • 挿入・削除のコストはノードの検索を除けば O(1)。
  1. ランダムアクセス
  • ArrayList:高速
    • 内部配列のインデックスを直接指定できるため、O(1) で任意の要素にアクセスできる。
  • LinkedList:遅い
    • インデックスでアクセスすると、先頭から順にノードをたどる必要があり、O(n) のコストがかかる。

ArrayListの使い方

以下のようにList<String> names = new ArrayList<>();で空のリストを作成できます。

import java.util.*;

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

        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        System.out.println(names.get(1));  // Bob
        names.remove("Alice");
        System.out.println(names);         // [Bob, Charlie]
    }
}

ちなみに、要素を持つリストを作成したい場合は次のようにします。

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));

Listの主なメソッド

add

末尾に要素を追加します。

List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
System.out.println(list); // [apple, banana]

以下のように指定したインデックスに要素を挿入することもできます。

List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add(1, "orange");
System.out.println(list); // [apple, orange, banana]

get

指定したインデックスの要素を返します。

// list = ["apple", "orange", "banana"]
String fruit = list.get(1);
System.out.println(fruit); // orange

set

指定したインデックスの要素を上書きします。
インデックスが存在しないと例外は発生します。

// list = ["apple", "orange", "banana"]
list.set(1, "grape");
System.out.println(list); // [apple, grape, banana]

remove

インデックスを指定して要素を削除します。
削除成功時にはtrueが返ってきます。

// list = ["apple", "grape", "banana"]
list.remove(1); // "grape" を削除
System.out.println(list); // [apple, banana]

また、オブジェクトを指定して削除もできます。

// list = ["apple", "banana"]
list.remove("apple");
System.out.println(list); // [banana]

removeIf

コレクションの要素を条件に基づいて削除できます。条件がtrueを返した要素は削除される。

list.removeIf(element -> 条件);

使用例

・リストから偶数要素を削除する。

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));

numbers.removeIf(n -> n % 2 == 0);  // 偶数を削除

System.out.println(numbers); // → [1, 3, 5]

・文字列のフィルタリング

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Admin", "Tom"));

names.removeIf(name -> name.startsWith("A"));

System.out.println(names); // → [Bob, Tom]

contains

リスト内に含まれているかを確認し、存在する時はtrue、存在しない場合にはfalseが返ってきます。

// list = ["banana"]
System.out.println(list.contains("banana")); // true
System.out.println(list.contains("peach"));  // false

size

リストの要素数を取得します。

// list = ["apple", "orange", "banana"]
System.out.println(list.size()); // 3

clear

要素が全て削除され、空の状態になります。

//list = ["apple", "orange", "banana"]
list.clear();
System.out.println(list); // []

isEmpty

空かどうかを判定します。size() == 0と同じだがこちらの方が読みやすい。

//list = []
System.out.println(list.isEmpty()); // true

ループの使用

拡張for文

リストに対するループの方法の中でも拡張for文はシンプルで安全な方法でよく使用されます。

import java.util.*;

public class ForEachExample {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));

        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

出力

Apple
Banana
Cherry

インデックス付きループ

インデックスが必要な場合はインデックス付きループを使用します。

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

forEach

Java8から、CollectionインターフェースにforEachメソッドが追加され、ラムダ式と一緒に使うことで簡潔にループ処理が書くことができます。

List<String> fruits = Arrays.asList("apple", "banana", "cherry");
fruits.forEach(fruit -> System.out.println(fruit));

また、以下のようにメソッド参照でラムダ式を簡略表記することができます。

fruits.forEach(System.out::println);

Stream API

Java8から登場したStreamは、コレクションのデータをパイプライン的に処理できるAPIです。複雑なデータ処理・集計・変換の際に使用します。元のコレクションは変更されず並列処理にも対応できます。

collection.stream()
          .filter(...)      // 中間操作
          .map(...)         // 中間操作
          .collect(...);    // 終端操作

・主な中間操作のメソッド

メソッド 説明
filter(Predicate) 条件に合う要素だけを通す
map(Function) 各要素を変換する
sorted() ソートする
distinct() 重複を排除する
limit(n)/skip() n件だけに制限/n件スキップする

・主な終端操作のメソッド

メソッド 説明
forEach(Consumer) 要素ごとに出力する
collect(Collectors.toList()) Listなどに変換する
count() 要素数カウントする
anyMatch()```,``` allMatch() 条件に合う要素の有無チェック
reduce() 集計処理

使用例

  1. 以下はaから始める要素を大文字にして新しいリストを作成しています。
List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        names.add("Anna");

List<String> result = names.stream()
    .filter(name -> name.startsWith("A"))     // "Alice", "Anna"
    .map(String::toUpperCase)                 // "ALICE", "ANNA"
    .collect(Collectors.toList());

System.out.println(result); // [ALICE, ANNA]
  1. 合計値を求める際の処理は以下のようになります。
    以下の.reduce(0, Integer::sum);は初期値を0とし、Integer::sumで2つのIntegerを加算するメソッド参照しています。((a, b) -> a + b と同じ)
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));

int sum = numbers.stream()
    .reduce(0, Integer::sum); // 初期値0で合計を計算

System.out.println(sum); // 15

上記のreduce()は以下のようになっています。

初期値 = 0
→ 0 + 1 = 1
→ 1 + 2 = 3
→ 3 + 3 = 6
→ 6 + 4 = 10
→ 10 + 5 = 15
  1. リストから偶数を抽出して平方に変換し、ソートして出力する処理は以下になります。
public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 4, 6);

        List<Integer> result = numbers.stream()
            .filter(n -> n % 2 == 0)     // 偶数フィルタ
            .map(n -> n * n)            // 平方に変換
            .sorted()                   // 昇順ソート
            .collect(Collectors.toList()); // リストに変換

        System.out.println(result);  // [4, 16, 36, 64]
    }
}

Discussion