Javaコレクションフレームワーク完全ガイド:List・Map・Setの活用術

2025-08-02

はじめに

Javaプログラミングにおいて、コレクションフレームワークはデータを効率的に管理・操作するための重要なツールです。配列だけでは対応が難しい動的なデータ操作や、複雑なデータ構造が必要な場面で威力を発揮します。この記事では、特に使用頻度の高いListMapSetインターフェースとその主要な実装クラス(ArrayListHashMapHashSetなど)について詳しく解説します。

コレクションフレームワークを学ぶメリット:

  • データの整理・管理が容易になる
  • パフォーマンスの良いデータ操作が可能
  • コードの可読性と保守性が向上する
  • アルゴリズム問題解決の幅が広がる

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

Javaのコレクションフレームワークは、主要なインターフェースとその実装クラスで構成されています。

図:Javaコレクションフレームワークの階層構造(簡略版)

主要インターフェースの分類

インターフェース説明主な実装クラス
List順序付けられた要素の集合(重複可)ArrayList, LinkedList
Set重複を許可しない要素の集合HashSet, TreeSet
Mapキーと値のペアを管理HashMap, TreeMap
QueueFIFO(先入れ先出し)構造LinkedList, PriorityQueue

Listインターフェース

Listの特徴

  • 要素の順序が保持される(挿入順)
  • 重複要素を許可する
  • インデックスによるアクセス可能

ArrayList – 最もよく使われるList実装

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

public class ArrayListExample {
    public static void main(String[] args) {
        // ArrayListの作成
        List fruits = new ArrayList<>();

        // 要素の追加
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 重複可

        // 要素の取得
        System.out.println(fruits.get(1)); // Banana

        // 要素の変更
        fruits.set(2, "Grape");

        // 要素の削除
        fruits.remove("Apple"); // 最初に見つかった要素を削除

        // サイズの取得
        System.out.println("サイズ: " + fruits.size());

        // 全要素の走査
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // 要素の存在チェック
        System.out.println("Bananaを含む? " + fruits.contains("Banana"));
    }
}

ArrayListの内部構造

ArrayListは内部的に配列を使用しており、以下のような特性があります:

  • 高速なランダムアクセス(O(1)時間)
  • 末尾への追加は高速(償却O(1)時間)
  • 途中への挿入・削除は比較的遅い(O(n)時間)
  • 容量が不足すると自動的に拡張(通常は1.5倍)

LinkedList – もう一つのList実装

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList numbers = new LinkedList<>();

        numbers.add(10);
        numbers.addFirst(5);  // 先頭に追加
        numbers.addLast(20);  // 末尾に追加

        System.out.println("先頭: " + numbers.getFirst());
        System.out.println("末尾: " + numbers.getLast());

        numbers.removeFirst();
        numbers.removeLast();
    }
}

ArrayList vs LinkedList

操作ArrayListLinkedList
取得(get)O(1)O(n)
追加(add)償却O(1)O(1)
挿入(add at index)O(n)O(1)(位置が既知の場合)
削除(remove)O(n)O(1)(位置が既知の場合)
メモリ使用量少ない(配列のみ)多い(ノードオブジェクト)

使い分けのポイント

  • 頻繁な検索やランダムアクセス → ArrayList
  • 頻繁な挿入・削除 → LinkedList
  • 一般的な用途ではArrayListがよく使われる

Mapインターフェース

Mapの特徴

  • キーと値のペアを管理
  • キーの重複は許可しない
  • 順序保証は実装依存(LinkedHashMapは挿入順、TreeMapはソート順)

HashMap – 最もよく使われるMap実装

import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        // HashMapの作成
        Map priceMap = new HashMap<>();

        // 要素の追加
        priceMap.put("Apple", 100);
        priceMap.put("Banana", 80);
        priceMap.put("Orange", 120);

        // 要素の取得
        System.out.println("Appleの価格: " + priceMap.get("Apple"));

        // 要素の存在チェック
        System.out.println("Mangoを含む? " + priceMap.containsKey("Mango"));
        System.out.println("価格100の商品がある? " + priceMap.containsValue(100));

        // 要素の上書き
        priceMap.put("Apple", 110); // 既存キーの値更新

        // 要素の削除
        priceMap.remove("Banana");

        // Mapの走査方法1: entrySet()
        for (Map.Entry entry : priceMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // Mapの走査方法2: keySet()
        for (String key : priceMap.keySet()) {
            System.out.println(key + ": " + priceMap.get(key));
        }

        // Mapの走査方法3: forEach (Java 8以降)
        priceMap.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

HashMapの内部構造

HashMapはハッシュテーブルをベースにした実装で:

  • キーのハッシュコードを使用して値を格納・検索
  • 平均O(1)時間でget/put操作
  • 衝突時はリンクリストまたはツリーで管理(Java 8以降)
  • 初期容量(デフォルト16)と負荷係数(デフォルト0.75)が重要

その他のMap実装

LinkedHashMap – 挿入順序またはアクセス順序を保持

Map orderedMap = new LinkedHashMap<>();

TreeMap – キーの自然順序またはComparatorでソート

Map sortedMap = new TreeMap<>();

Setインターフェース

Setの特徴

  • 重複要素を許可しない
  • 順序保証は実装依存
  • 数学的な集合操作をサポート

HashSet – 最もよく使われるSet実装

import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        Set uniqueNames = new HashSet<>();

        // 要素の追加
        uniqueNames.add("Alice");
        uniqueNames.add("Bob");
        uniqueNames.add("Alice"); // 重複は無視

        System.out.println("要素数: " + uniqueNames.size()); // 2

        // 要素の存在チェック
        System.out.println("Aliceを含む? " + uniqueNames.contains("Alice"));

        // 要素の削除
        uniqueNames.remove("Bob");

        // 集合の走査
        for (String name : uniqueNames) {
            System.out.println(name);
        }

        // 他の集合操作
        Set otherNames = Set.of("Alice", "Charlie");

        // 和集合
        Set union = new HashSet<>(uniqueNames);
        union.addAll(otherNames);

        // 積集合
        Set intersection = new HashSet<>(uniqueNames);
        intersection.retainAll(otherNames);

        // 差集合
        Set difference = new HashSet<>(uniqueNames);
        difference.removeAll(otherNames);
    }
}

HashSetの内部構造

HashSetは内部的にHashMapを使用しており:

  • 要素の一意性を保証(equals()とhashCode()に依存)
  • null要素を1つだけ許可
  • 順序は保証されない

その他のSet実装

LinkedHashSet – 挿入順序を保持

Set orderedSet = new LinkedHashSet<>();

TreeSet – 要素を自然順序またはComparatorでソート

Set sortedSet = new TreeSet<>();

ジェネリクスと型安全

Javaのコレクションはジェネリクスを使用して型安全を実現しています。

// 非ジェネリック(古い書き方 - 非推奨)
List rawList = new ArrayList();
rawList.add("文字列");
rawList.add(10); // コンパイルは通るが危険

// ジェネリックを使用(推奨)
List stringList = new ArrayList<>();
stringList.add("文字列");
// stringList.add(10); // コンパイルエラー

便利なユーティリティメソッド

Collectionsクラスの活用

import java.util.Collections;

List numbers = new ArrayList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);

// ソート
Collections.sort(numbers);

// 逆順ソート
Collections.sort(numbers, Collections.reverseOrder());

// シャッフル
Collections.shuffle(numbers);

// 不変(immutable)なコレクション作成
List immutableList = Collections.unmodifiableList(numbers);
Set immutableSet = Collections.unmodifiableSet(new HashSet<>());

Listから配列への変換

List list = List.of("A", "B", "C");
String[] array = list.toArray(new String[0]);

配列からListへの変換

String[] array = {"A", "B", "C"};
List list = Arrays.asList(array); // 固定サイズのList
List mutableList = new ArrayList<>(Arrays.asList(array)); // 可変のList

Java 8以降の新機能

Stream APIとの連携

import java.util.stream.Collectors;

List fruits = List.of("Apple", "Banana", "Orange", "Avocado");

// フィルタリング
List aFruits = fruits.stream()
    .filter(f -> f.startsWith("A"))
    .collect(Collectors.toList());

// マッピング
List lengths = fruits.stream()
    .map(String::length)
    .collect(Collectors.toList());

// グルーピング
Map> lengthMap = fruits.stream()
    .collect(Collectors.groupingBy(String::length));

便利なファクトリメソッド

// 不変(immutable)なコレクション作成
List immutableList = List.of("A", "B", "C");
Set immutableSet = Set.of(1, 2, 3);
Map immutableMap = Map.of("A", 1, "B", 2);

// 注意:これらのコレクションは変更不可(add/removeできない)

パフォーマンス考慮事項

コレクション選択のガイドライン

  1. 頻繁な検索が必要HashSet または HashMap
  2. 順序保持が必要LinkedHashSet または LinkedHashMap
  3. ソートが必要TreeSet または TreeMap
  4. 頻繁な挿入・削除LinkedList(Listの場合)
  5. 一般的な用途ArrayList(Listの場合)、HashMap(Mapの場合)

初期容量の設定

大規模なコレクションを使用する場合、初期容量を設定することでリサイズのコストを削減できます。

// 初期容量100、負荷係数0.8のHashMap
Map largeMap = new HashMap<>(100, 0.8f);

// 初期容量50のArrayList
List largeList = new ArrayList<>(50);

実践的な使用例

単語カウントプログラム

import java.util.*;

public class WordCounter {
    public static void main(String[] args) {
        String text = "apple banana apple orange banana apple";

        Map wordCount = new HashMap<>();

        for (String word : text.split(" ")) {
            wordCount.merge(word, 1, Integer::sum);
            // または
            // wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
        }

        wordCount.forEach((word, count) -> 
            System.out.println(word + ": " + count));
    }
}

学生成績管理システム

import java.util.*;

public class GradeManager {
    private Map> studentGrades = new HashMap<>();

    public void addGrade(String studentName, int grade) {
        studentGrades.computeIfAbsent(studentName, k -> new ArrayList<>()).add(grade);
    }

    public double getAverage(String studentName) {
        List grades = studentGrades.get(studentName);
        if (grades == null || grades.isEmpty()) return 0;

        return grades.stream()
            .mapToInt(Integer::intValue)
            .average()
            .orElse(0);
    }

    public Set getTopStudents(int n) {
        return studentGrades.entrySet().stream()
            .sorted(Comparator.comparingDouble(
                e -> -getAverage(e.getKey()))) // 降順ソート
            .limit(n)
            .map(Map.Entry::getKey)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    }
}

よくある間違いとベストプラクティス

よくある間違い

  1. イテレーション中のコレクション変更
   List list = new ArrayList<>(List.of("A", "B", "C"));
   for (String s : list) {
       if (s.equals("B")) {
           list.remove(s); // ConcurrentModificationException
       }
   }
  1. equalsとhashCodeの不整合
   class Person {
       String name;
       // equalsをオーバーライドしたがhashCodeをオーバーライドしない
       // → HashSetやHashMapで問題が発生
   }
  1. 可変オブジェクトをキーに使用
   Map, String> map = new HashMap<>();
   List key = new ArrayList<>();
   key.add("a");
   map.put(key, "value");
   key.add("b"); // キーが変更されたため、マップが壊れる

ベストプラクティス

  1. インターフェース型で変数を宣言
   List list = new ArrayList<>(); // 良い
   ArrayList list = new ArrayList<>(); // 悪くはないが柔軟性に欠ける
  1. 不変コレクションを活用
   List immutable = Collections.unmodifiableList(mutableList);
  1. 容量を事前に設定(大規模なコレクションの場合)
   new ArrayList<>(1000); // 約1000要素が予想される場合
  1. Java 8以降のAPIを活用
   map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

練習問題

理解を深めるための練習問題を用意しました。実際にコードを書いて試してみましょう。

問題1: 重複要素の削除

整数のListを受け取り、重複を削除したListを返すメソッドを作成してください。順序は保持するものとします。

問題2: 単語頻度カウンター

テキストを受け取り、各単語の出現頻度を降順で表示するプログラムを作成してください。

問題3: 共通要素検出

2つのListを受け取り、両方に含まれる要素のSetを返すメソッドを作成してください。

問題4: キャッシュシステム

最近使用された順に要素を保持するLRU(Least Recently Used)キャッシュをLinkedHashMapを使用して実装してください。キャッシュの最大サイズを指定できるようにします。

問題5: 学生成績分析

学生の名前とテストスコアのリストを受け取り、以下の情報を提供するプログラムを作成してください:

  • 各学生の平均点
  • クラス全体の平均点
  • 最高得点の学生
  • 科目ごとの平均点

次のステップ

コレクションフレームワークの基礎を理解したら、以下のトピックに進みましょう:

  1. 並行コレクションConcurrentHashMap, CopyOnWriteArrayList
  2. Java Stream API – 関数型スタイルのデータ処理
  3. 不変(immutable)コレクション – 完全に不変なコレクションの作成
  4. カスタムコレクションの実装 – 特定のニーズに合わせたコレクション作成

さらに学ぶためのリソース:

まとめ

この記事では、Javaのコレクションフレームワークの中核であるListMapSetについて詳しく解説しました。重要なポイントをまとめます:

  1. List – 順序付けられた要素の集合(重複可)、ArrayListLinkedListが主要実装
  2. Map – キーと値のペアを管理、HashMapが最も一般的
  3. Set – 重複を許可しない要素の集合、HashSetが基本実装
  4. 適切なコレクション選択がパフォーマンスとコードの品質に大きく影響
  5. Java 8以降の新機能(Stream APIなど)を活用してより簡潔なコードを記述

コレクションフレームワークはJavaプログラミングの基盤です。実際のプロジェクトで積極的に活用し、その便利さと強力さを実感してください。最初はシンプルな使い方から始め、徐々に高度な機能を習得していくのがおすすめです。Happy coding!