はじめに

Javaで複数のデータを効率的に管理するには、コレクションフレームワークの理解が不可欠です。配列は固定長で機能が限られていますが、コレクションを使えば可変長のデータを柔軟に追加・削除・検索できます。

この記事では、Collectionインターフェースの階層構造から、ArrayList・LinkedList・HashSet・TreeSet・HashMap・TreeMapといった主要な実装クラスの特徴と使い分け、そしてJava 21で導入されたSequenced Collectionsまで、段階的に解説します。

コレクションフレームワークの全体像

コレクションフレームワークは、データの集合を扱うための統一されたアーキテクチャです。配列と比較して以下のメリットがあります。

特徴 配列 コレクション
サイズ 固定長 可変長(自動拡張)
格納できる型 プリミティブ型・参照型 参照型のみ
提供される機能 length、添字アクセスのみ 追加・削除・検索・ソートなど多数
重複の制御 不可 Setで重複排除可能

インターフェースの階層構造

コレクションフレームワークは、用途に応じた複数のインターフェースで構成されています。

graph TD
    subgraph "Collectionインターフェース系統"
        Iterable["Iterable<E>"]
        Collection["Collection<E>"]
        List["List<E>"]
        Set["Set<E>"]
        Queue["Queue<E>"]
        SortedSet["SortedSet<E>"]
        NavigableSet["NavigableSet<E>"]
        Deque["Deque<E>"]
        
        Iterable --> Collection
        Collection --> List
        Collection --> Set
        Collection --> Queue
        Set --> SortedSet
        SortedSet --> NavigableSet
        Queue --> Deque
    end
    
    subgraph "Mapインターフェース系統"
        Map["Map<K,V>"]
        SortedMap["SortedMap<K,V>"]
        NavigableMap["NavigableMap<K,V>"]
        
        Map --> SortedMap
        SortedMap --> NavigableMap
    end

主要なインターフェースの役割

インターフェース 役割 特徴
Collection コレクションの基本操作 add、remove、contains等
List 順序付きのコレクション インデックスによるアクセス、重複を許可
Set 重複を許さないコレクション 数学的な集合の概念
Queue 先入れ先出し(FIFO)のコレクション キュー操作(offer、poll、peek)
Deque 両端キュー 両端からの追加・削除が可能
Map キーと値のペアを管理 キーによる高速検索

Listインターフェース

Listは要素の順序を保持し、重複を許可するコレクションです。インデックス(0始まり)を使って要素にアクセスできます。

ArrayListの特徴と使い方

ArrayListは内部で可変長配列を使用した実装です。ランダムアクセスが高速で、最も一般的に使用されるListの実装クラスです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        // ArrayListの作成
        List<String> fruits = new ArrayList<>();
        
        // 要素の追加
        fruits.add("りんご");
        fruits.add("バナナ");
        fruits.add("オレンジ");
        fruits.add("りんご");  // 重複OK
        
        System.out.println(fruits);  // [りんご, バナナ, オレンジ, りんご]
        
        // インデックスによるアクセス(O(1))
        String first = fruits.get(0);
        System.out.println("最初の要素: " + first);  // りんご
        
        // 要素の更新
        fruits.set(1, "ぶどう");
        System.out.println(fruits);  // [りんご, ぶどう, オレンジ, りんご]
        
        // 要素の削除(インデックス指定)
        fruits.remove(2);
        System.out.println(fruits);  // [りんご, ぶどう, りんご]
        
        // 要素数の取得
        System.out.println("要素数: " + fruits.size());  // 3
    }
}

ArrayListのパフォーマンス特性

操作 計算量 説明
get(index) O(1) インデックスによる直接アクセス
add(element) O(1) 償却 末尾への追加(容量拡張時はO(n))
add(index, element) O(n) 途中への挿入(要素のシフトが必要)
remove(index) O(n) 削除後に要素のシフトが必要
contains(element) O(n) 線形検索

初期容量の指定

大量のデータを格納することがわかっている場合は、初期容量を指定することでパフォーマンスを向上できます。

1
2
3
4
5
6
7
// 初期容量を1000に設定
List<Integer> numbers = new ArrayList<>(1000);

// 容量拡張による再配置を避けられる
for (int i = 0; i < 1000; i++) {
    numbers.add(i);
}

LinkedListの特徴と使い方

LinkedList双方向連結リストで実装されています。先頭・末尾への追加・削除が高速ですが、ランダムアクセスは低速です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();
        
        // 先頭・末尾への追加(O(1))
        tasks.addFirst("タスクA");
        tasks.addLast("タスクB");
        tasks.add("タスクC");  // 末尾に追加
        
        System.out.println(tasks);  // [タスクA, タスクB, タスクC]
        
        // 先頭・末尾の取得
        System.out.println("先頭: " + tasks.getFirst());  // タスクA
        System.out.println("末尾: " + tasks.getLast());   // タスクC
        
        // 先頭・末尾からの削除(O(1))
        String removed = tasks.removeFirst();
        System.out.println("削除: " + removed);  // タスクA
        System.out.println(tasks);  // [タスクB, タスクC]
    }
}

LinkedListのパフォーマンス特性

操作 計算量 説明
get(index) O(n) 先頭または末尾から順に辿る
addFirst/addLast O(1) 先頭・末尾への追加
add(index, element) O(n) 挿入位置まで辿る必要がある
removeFirst/removeLast O(1) 先頭・末尾からの削除

ArrayListとLinkedListの使い分け

graph TD
    A["データの操作パターンは?"] --> B{"ランダムアクセスが多い"}
    A --> C{"先頭・末尾の追加削除が多い"}
    A --> D{"途中への挿入削除が多い"}
    
    B --> E["ArrayList を選択"]
    C --> F["LinkedList を選択"]
    D --> G["要素数が少なければ ArrayList<br/>多ければ LinkedList を検討"]
    
    style E fill:#90EE90
    style F fill:#87CEEB
    style G fill:#FFE4B5

実務ではほとんどの場合ArrayListが適しています。LinkedListはメモリオーバーヘッドが大きく、CPUキャッシュ効率も悪いため、先頭・末尾の操作が頻繁な場合のみ検討してください。

Setインターフェース

Set重複を許さないコレクションです。数学的な「集合」の概念を表現しており、同じ要素は1つしか存在できません。

HashSetの特徴と使い方

HashSetハッシュテーブルで実装されており、要素の追加・削除・検索が非常に高速です。ただし、順序は保証されません

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        Set<String> colors = new HashSet<>();
        
        // 要素の追加
        colors.add("赤");
        colors.add("青");
        colors.add("緑");
        colors.add("赤");  // 重複は無視される
        
        System.out.println(colors);  // [赤, 緑, 青](順序は不定)
        System.out.println("要素数: " + colors.size());  // 3
        
        // 要素の存在確認(O(1))
        System.out.println("赤は含まれる: " + colors.contains("赤"));  // true
        System.out.println("黄は含まれる: " + colors.contains("黄"));  // false
        
        // 要素の削除
        colors.remove("青");
        System.out.println(colors);  // [赤, 緑]
    }
}

HashSetのパフォーマンス特性

操作 計算量 説明
add(element) O(1) ハッシュ値による直接配置
remove(element) O(1) ハッシュ値による直接アクセス
contains(element) O(1) ハッシュ値による直接検索

カスタムオブジェクトとequals/hashCode

HashSetでカスタムオブジェクトを使用する場合、equals()hashCode()を適切にオーバーライドする必要があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class CustomObjectHashSet {
    public static void main(String[] args) {
        Set<User> users = new HashSet<>();
        
        users.add(new User(1, "田中"));
        users.add(new User(2, "鈴木"));
        users.add(new User(1, "田中"));  // 重複(追加されない)
        
        System.out.println("ユーザー数: " + users.size());  // 2
    }
}

class User {
    private int id;
    private String name;
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id && Objects.equals(name, user.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

LinkedHashSetで挿入順序を保持

LinkedHashSetは内部でハッシュテーブルと双方向連結リストを組み合わせており、挿入順序を保持します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        Set<String> orderedColors = new LinkedHashSet<>();
        
        orderedColors.add("赤");
        orderedColors.add("青");
        orderedColors.add("緑");
        
        // 挿入順序が保持される
        System.out.println(orderedColors);  // [赤, 青, 緑]
    }
}

TreeSetの特徴と使い方

TreeSet赤黒木(Red-Black Tree) で実装されており、要素が自然順序(昇順) でソートされます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.util.Set;
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        Set<Integer> numbers = new TreeSet<>();
        
        numbers.add(30);
        numbers.add(10);
        numbers.add(50);
        numbers.add(20);
        numbers.add(40);
        
        // 自動的にソートされる
        System.out.println(numbers);  // [10, 20, 30, 40, 50]
    }
}

TreeSetの追加機能(NavigableSet)

TreeSetNavigableSetインターフェースを実装しており、範囲検索などの高度な操作が可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.NavigableSet;
import java.util.TreeSet;

public class NavigableSetExample {
    public static void main(String[] args) {
        NavigableSet<Integer> numbers = new TreeSet<>();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        numbers.add(40);
        numbers.add(50);
        
        // 指定値以下で最大の要素
        System.out.println("floor(25): " + numbers.floor(25));  // 20
        
        // 指定値以上で最小の要素
        System.out.println("ceiling(25): " + numbers.ceiling(25));  // 30
        
        // 部分集合の取得
        System.out.println("subSet(20, 40): " + numbers.subSet(20, 40));  // [20, 30]
        
        // 降順ビュー
        System.out.println("降順: " + numbers.descendingSet());  // [50, 40, 30, 20, 10]
    }
}

TreeSetのパフォーマンス特性

操作 計算量 説明
add(element) O(log n) ツリーへの挿入
remove(element) O(log n) ツリーからの削除
contains(element) O(log n) ツリーの探索
first() / last() O(log n) 最小/最大要素の取得

カスタムComparatorでソート順を指定

デフォルトの自然順序とは異なるソート順を指定する場合、Comparatorを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class CustomComparatorTreeSet {
    public static void main(String[] args) {
        // 降順でソート
        Set<String> descendingNames = new TreeSet<>(Comparator.reverseOrder());
        
        descendingNames.add("佐藤");
        descendingNames.add("田中");
        descendingNames.add("鈴木");
        
        System.out.println(descendingNames);  // [鈴木, 田中, 佐藤]
        
        // 文字列長でソート
        Set<String> byLength = new TreeSet<>(Comparator.comparingInt(String::length));
        
        byLength.add("りんご");
        byLength.add("ぶどう");
        byLength.add("いちご");
        
        // 長さが同じ場合は最初の要素のみ保持される点に注意
        System.out.println(byLength);  // [りんご]
    }
}

Mapインターフェース

Mapキーと値のペアを管理するデータ構造です。キーは重複できませんが、値は重複できます。

HashMapの特徴と使い方

HashMapはハッシュテーブルで実装されており、キーによる値の取得が非常に高速です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        
        // エントリの追加
        scores.put("田中", 85);
        scores.put("鈴木", 90);
        scores.put("佐藤", 78);
        
        // 値の取得(O(1))
        System.out.println("田中の点数: " + scores.get("田中"));  // 85
        
        // 存在しないキー
        System.out.println("山田の点数: " + scores.get("山田"));  // null
        
        // デフォルト値付きの取得
        int yamadaScore = scores.getOrDefault("山田", 0);
        System.out.println("山田の点数(デフォルト): " + yamadaScore);  // 0
        
        // キーの存在確認
        System.out.println("鈴木は存在: " + scores.containsKey("鈴木"));  // true
        
        // 値の更新
        scores.put("田中", 95);  // 上書き
        System.out.println("田中の新しい点数: " + scores.get("田中"));  // 95
        
        // エントリの削除
        scores.remove("佐藤");
        System.out.println(scores);  // {田中=95, 鈴木=90}
    }
}

HashMapの便利なメソッド

Java 8以降、HashMapには便利なメソッドが多数追加されています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.HashMap;
import java.util.Map;

public class HashMapAdvanced {
    public static void main(String[] args) {
        Map<String, Integer> wordCount = new HashMap<>();
        
        String[] words = {"apple", "banana", "apple", "orange", "banana", "apple"};
        
        // 従来の書き方
        for (String word : words) {
            if (wordCount.containsKey(word)) {
                wordCount.put(word, wordCount.get(word) + 1);
            } else {
                wordCount.put(word, 1);
            }
        }
        
        wordCount.clear();
        
        // merge を使った簡潔な書き方
        for (String word : words) {
            wordCount.merge(word, 1, Integer::sum);
        }
        
        System.out.println(wordCount);  // {orange=1, banana=2, apple=3}
        
        // computeIfAbsent: キーが存在しない場合のみ計算して追加
        Map<String, StringBuilder> logs = new HashMap<>();
        logs.computeIfAbsent("info", k -> new StringBuilder()).append("初期化完了 ");
        logs.computeIfAbsent("info", k -> new StringBuilder()).append("処理開始 ");
        
        System.out.println(logs.get("info"));  // 初期化完了 処理開始
    }
}

LinkedHashMapで挿入順序を保持

LinkedHashMapは挿入順序を保持するHashMapです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        Map<String, String> capitals = new LinkedHashMap<>();
        
        capitals.put("日本", "東京");
        capitals.put("アメリカ", "ワシントンD.C.");
        capitals.put("イギリス", "ロンドン");
        
        // 挿入順序が保持される
        for (Map.Entry<String, String> entry : capitals.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
        // 日本 -> 東京
        // アメリカ -> ワシントンD.C.
        // イギリス -> ロンドン
    }
}

TreeMapの特徴と使い方

TreeMapはキーを自然順序(昇順) でソートして保持します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Map;
import java.util.TreeMap;

public class TreeMapExample {
    public static void main(String[] args) {
        Map<Integer, String> rankingMap = new TreeMap<>();
        
        rankingMap.put(3, "銅メダル");
        rankingMap.put(1, "金メダル");
        rankingMap.put(2, "銀メダル");
        
        // キーでソートされる
        for (Map.Entry<Integer, String> entry : rankingMap.entrySet()) {
            System.out.println(entry.getKey() + "位: " + entry.getValue());
        }
        // 1位: 金メダル
        // 2位: 銀メダル
        // 3位: 銅メダル
    }
}

TreeMapの追加機能(NavigableMap)

TreeMapNavigableMapインターフェースを実装しており、範囲検索が可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.NavigableMap;
import java.util.TreeMap;

public class NavigableMapExample {
    public static void main(String[] args) {
        NavigableMap<Integer, String> studentGrades = new TreeMap<>();
        
        studentGrades.put(60, "可");
        studentGrades.put(70, "良");
        studentGrades.put(80, "優");
        studentGrades.put(90, "秀");
        
        int score = 75;
        
        // score以下で最大のキーに対応する値
        var entry = studentGrades.floorEntry(score);
        System.out.println(score + "点の評価: " + entry.getValue());  // 良
        
        // 部分マップの取得(70点以上80点未満)
        System.out.println("部分マップ: " + studentGrades.subMap(70, 80));  // {70=良}
    }
}

Mapのパフォーマンス比較

実装クラス get put 順序 用途
HashMap O(1) O(1) なし 高速な検索が必要な場合
LinkedHashMap O(1) O(1) 挿入順 順序を保持したい場合
TreeMap O(log n) O(log n) キー順 ソートや範囲検索が必要な場合

コレクションの操作

コレクションを効率的に操作するための基本的なパターンを紹介します。

要素の追加と削除

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CollectionOperations {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        
        // 単一要素の追加
        list.add("A");
        list.add("B");
        
        // 複数要素の追加
        list.addAll(Arrays.asList("C", "D", "E"));
        
        System.out.println(list);  // [A, B, C, D, E]
        
        // 条件に合う要素を削除(Java 8以降)
        list.removeIf(s -> s.equals("C"));
        
        System.out.println(list);  // [A, B, D, E]
        
        // すべての要素を削除
        list.clear();
        System.out.println("要素数: " + list.size());  // 0
    }
}

ループ処理のパターン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class IterationPatterns {
    public static void main(String[] args) {
        List<String> fruits = List.of("りんご", "バナナ", "オレンジ");
        
        // 拡張for文(推奨)
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        
        // forEach(ラムダ式)
        fruits.forEach(fruit -> System.out.println(fruit));
        
        // forEachとメソッド参照
        fruits.forEach(System.out::println);
        
        // インデックスが必要な場合
        for (int i = 0; i < fruits.size(); i++) {
            System.out.println(i + ": " + fruits.get(i));
        }
        
        // Mapのループ
        Map<String, Integer> scores = new HashMap<>();
        scores.put("田中", 85);
        scores.put("鈴木", 90);
        
        // entrySetでループ(推奨)
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // forEachでループ
        scores.forEach((name, score) -> 
            System.out.println(name + ": " + score));
    }
}

ループ中の要素削除

ループ中に要素を削除する場合は、Iteratorを使用するか、removeIfを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveDuringIteration {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
        
        // 方法1: Iteratorを使用
        Iterator<Integer> iterator = numbers.iterator();
        while (iterator.hasNext()) {
            if (iterator.next() % 2 == 0) {
                iterator.remove();
            }
        }
        System.out.println(numbers);  // [1, 3, 5]
        
        // 方法2: removeIfを使用(推奨)
        List<Integer> numbers2 = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
        numbers2.removeIf(n -> n % 2 == 0);
        System.out.println(numbers2);  // [1, 3, 5]
    }
}

不変コレクションの作成

Java 9以降、List.of()Set.of()Map.of()で不変コレクションを簡単に作成できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ImmutableCollections {
    public static void main(String[] args) {
        // 不変List
        List<String> immutableList = List.of("A", "B", "C");
        
        // 不変Set
        Set<Integer> immutableSet = Set.of(1, 2, 3);
        
        // 不変Map
        Map<String, Integer> immutableMap = Map.of(
            "one", 1,
            "two", 2,
            "three", 3
        );
        
        // 変更しようとすると UnsupportedOperationException
        try {
            immutableList.add("D");
        } catch (UnsupportedOperationException e) {
            System.out.println("不変コレクションは変更できません");
        }
    }
}

Sequenced Collections(Java 21の新機能)

Java 21では、Sequenced Collectionsという新しいインターフェース群が導入されました。これにより、順序を持つコレクションに対して統一的なAPIで先頭・末尾の操作や逆順処理が可能になりました。

従来の問題点

Java 21以前は、コレクションの最初・最後の要素を取得する方法がバラバラでした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Java 20以前の書き方(統一性がない)
List<String> list = new ArrayList<>();
String first = list.get(0);                    // List
String last = list.get(list.size() - 1);       // List(煩雑)

Deque<String> deque = new ArrayDeque<>();
String first2 = deque.getFirst();              // Deque
String last2 = deque.getLast();                // Deque

SortedSet<String> sortedSet = new TreeSet<>();
String first3 = sortedSet.first();             // SortedSet
String last3 = sortedSet.last();               // SortedSet

LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
String first4 = linkedHashSet.iterator().next();  // 先頭のみ可能
// 末尾の取得は困難...

SequencedCollectionインターフェース

Java 21では、SequencedCollectionインターフェースが追加され、順序を持つすべてのコレクションで統一的なAPIを使用できるようになりました。

graph TD
    subgraph "Java 21 Sequenced Collections"
        SC["SequencedCollection&lt;E&gt;"]
        SS["SequencedSet&lt;E&gt;"]
        SM["SequencedMap&lt;K,V&gt;"]
        
        Collection --> SC
        SC --> List
        SC --> Deque
        SC --> SS
        SS --> SortedSet
        SS --> LinkedHashSet
        
        Map --> SM
        SM --> SortedMap
        SM --> LinkedHashMap
    end

SequencedCollectionの主要メソッド

メソッド 説明
getFirst() 最初の要素を取得
getLast() 最後の要素を取得
addFirst(E) 先頭に要素を追加
addLast(E) 末尾に要素を追加
removeFirst() 先頭の要素を削除して返す
removeLast() 末尾の要素を削除して返す
reversed() 逆順のビューを返す

SequencedCollectionの使用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.SequencedCollection;

public class SequencedCollectionExample {
    public static void main(String[] args) {
        // ArrayList(Listの実装)
        var list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        
        // 統一されたAPIで先頭・末尾にアクセス
        System.out.println("List先頭: " + list.getFirst());  // A
        System.out.println("List末尾: " + list.getLast());   // C
        
        // LinkedHashSetでも同じAPI
        var linkedSet = new LinkedHashSet<String>();
        linkedSet.add("X");
        linkedSet.add("Y");
        linkedSet.add("Z");
        
        System.out.println("Set先頭: " + linkedSet.getFirst());  // X
        System.out.println("Set末尾: " + linkedSet.getLast());   // Z
        
        // 先頭・末尾への追加
        list.addFirst("先頭");
        list.addLast("末尾");
        System.out.println(list);  // [先頭, A, B, C, 末尾]
    }
}

reversed()メソッドによる逆順処理

reversed()メソッドは、元のコレクションの逆順ビューを返します。ビューなので、新しいコレクションは作成されません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.ArrayList;
import java.util.List;

public class ReversedExample {
    public static void main(String[] args) {
        var numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5));
        
        // 逆順ビューの取得
        var reversed = numbers.reversed();
        
        // 逆順でループ
        System.out.println("逆順:");
        for (int n : reversed) {
            System.out.print(n + " ");  // 5 4 3 2 1
        }
        System.out.println();
        
        // Streamでも逆順処理が簡単に
        numbers.reversed().stream()
               .forEach(n -> System.out.print(n + " "));  // 5 4 3 2 1
        System.out.println();
        
        // 逆順ビューへの変更は元のコレクションに反映される
        reversed.addFirst(100);  // 逆順の先頭 = 元の末尾
        System.out.println("元のリスト: " + numbers);  // [1, 2, 3, 4, 5, 100]
    }
}

SequencedMapの使用例

SequencedMapMapに対する順序付き操作を提供します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.LinkedHashMap;
import java.util.SequencedMap;

public class SequencedMapExample {
    public static void main(String[] args) {
        SequencedMap<String, Integer> map = new LinkedHashMap<>();
        
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        
        // 最初と最後のエントリ
        System.out.println("最初のエントリ: " + map.firstEntry());  // A=1
        System.out.println("最後のエントリ: " + map.lastEntry());   // C=3
        
        // 先頭・末尾への追加
        map.putFirst("Z", 0);  // 先頭に追加
        map.putLast("D", 4);   // 末尾に追加
        
        System.out.println("マップ: " + map);  // {Z=0, A=1, B=2, C=3, D=4}
        
        // 逆順ビュー
        System.out.println("逆順: " + map.reversed());  // {D=4, C=3, B=2, A=1, Z=0}
        
        // sequencedKeySet(), sequencedValues(), sequencedEntrySet()
        System.out.println("キー(順序付き): " + map.sequencedKeySet());
    }
}

コレクションの使い分けガイド

用途に応じた最適なコレクションを選択するためのガイドです。

List実装の選択

graph TD
    A["Listが必要"] --> B{"ランダムアクセス<br/>(インデックスによる取得)<br/>が頻繁?"}
    B -->|Yes| C["ArrayList"]
    B -->|No| D{"先頭・末尾の<br/>追加削除が頻繁?"}
    D -->|Yes| E["LinkedList"]
    D -->|No| C
    
    style C fill:#90EE90
    style E fill:#87CEEB

結論: ほとんどの場合はArrayListを選択してください。LinkedListはメモリ効率が悪く、実務で使用するケースは限定的です。

Set実装の選択

graph TD
    A["Setが必要"] --> B{"ソートが必要?"}
    B -->|Yes| C["TreeSet"]
    B -->|No| D{"挿入順序を<br/>保持したい?"}
    D -->|Yes| E["LinkedHashSet"]
    D -->|No| F["HashSet"]
    
    style C fill:#FFB6C1
    style E fill:#DDA0DD
    style F fill:#90EE90
実装 順序 計算量 用途
HashSet なし O(1) 高速な重複排除
LinkedHashSet 挿入順 O(1) 順序を保持した重複排除
TreeSet ソート順 O(log n) ソートされた集合、範囲検索

Map実装の選択

graph TD
    A["Mapが必要"] --> B{"キーのソートが必要?"}
    B -->|Yes| C["TreeMap"]
    B -->|No| D{"挿入順序を<br/>保持したい?"}
    D -->|Yes| E["LinkedHashMap"]
    D -->|No| F["HashMap"]
    
    style C fill:#FFB6C1
    style E fill:#DDA0DD
    style F fill:#90EE90
実装 順序 計算量 用途
HashMap なし O(1) 高速なキー・値検索
LinkedHashMap 挿入順 O(1) 順序を保持したマップ、LRUキャッシュ
TreeMap キー順 O(log n) ソートされたマップ、範囲検索

実務でのベストプラクティス

  1. インターフェース型で宣言する
1
2
3
4
5
6
7
// 推奨:インターフェース型で宣言
List<String> names = new ArrayList<>();
Set<Integer> ids = new HashSet<>();
Map<String, Object> config = new HashMap<>();

// 非推奨:実装クラスで宣言
ArrayList<String> names = new ArrayList<>();  // 避ける
  1. 初期容量を指定する
1
2
3
// 大量のデータを格納する場合は初期容量を指定
List<String> largeList = new ArrayList<>(10000);
Map<String, Integer> largeMap = new HashMap<>(10000);
  1. 不変コレクションを活用する
1
2
// 変更されないコレクションは不変にする
List<String> constants = List.of("ADMIN", "USER", "GUEST");
  1. 適切な実装を選択する
要件 推奨実装
一般的なリスト操作 ArrayList
重複排除 HashSet
重複排除 + 順序保持 LinkedHashSet
ソート済みの集合 TreeSet
キー・値のペア HashMap
キー・値のペア + 順序保持 LinkedHashMap
キーでソートされたマップ TreeMap

まとめ

この記事では、Javaコレクションフレームワークの全体像から、各実装クラスの特徴と使い分け、そしてJava 21のSequenced Collectionsまで解説しました。

主なポイントは以下の通りです。

  • List: 順序付きで重複を許可。ほとんどの場合はArrayListを選択
  • Set: 重複を許さない集合。HashSetが最も高速、TreeSetでソート
  • Map: キーと値のペア。HashMapが最も高速、TreeMapでキーソート
  • Sequenced Collections: Java 21で追加された統一APIで先頭・末尾の操作が簡単に

コレクションの特性を理解し、用途に応じて適切な実装を選択することで、効率的で保守しやすいプログラムを作成できます。

参考リンク