Day 9: コレクションとジェネリクス
今日学ぶこと
- List(ArrayList, LinkedList)
- Set(HashSet, TreeSet)
- Map(HashMap, TreeMap)
- ジェネリクス
- Stream API の基礎
コレクションフレームワーク
Javaのコレクションフレームワークは、データの集まりを扱う統一されたAPIです。
flowchart TB
Collection["«interface»<br>Collection"]
List["«interface»<br>List"]
Set["«interface»<br>Set"]
Map["«interface»<br>Map"]
ArrayList["ArrayList"]
LinkedList["LinkedList"]
HashSet["HashSet"]
TreeSet["TreeSet"]
HashMap["HashMap"]
TreeMap["TreeMap"]
Collection --> List
Collection --> Set
List --> ArrayList
List --> LinkedList
Set --> HashSet
Set --> TreeSet
Map --> HashMap
Map --> TreeMap
style Collection fill:#3b82f6,color:#fff
style List fill:#22c55e,color:#fff
style Set fill:#f59e0b,color:#fff
style Map fill:#8b5cf6,color:#fff
List
順序あり・重複OKのコレクションです。
ArrayList
import java.util.ArrayList;
import java.util.List;
// 作成
List<String> fruits = new ArrayList<>();
// 追加
fruits.add("りんご");
fruits.add("みかん");
fruits.add("ぶどう");
fruits.add("りんご"); // 重複OK
// アクセス
fruits.get(0) // "りんご"
fruits.size() // 4
fruits.contains("みかん") // true
// 変更・削除
fruits.set(1, "バナナ"); // インデックス1を変更
fruits.remove("ぶどう"); // 値で削除
fruits.remove(0); // インデックスで削除
// ループ
for (String fruit : fruits) {
System.out.println(fruit);
}
不変リスト(Java 9+)
// 変更不可のリスト
List<String> colors = List.of("赤", "青", "緑");
// colors.add("黄"); // ❌ UnsupportedOperationException
主要メソッド
| メソッド | 説明 |
|---|---|
add(e) |
末尾に追加 |
add(i, e) |
指定位置に挿入 |
get(i) |
取得 |
set(i, e) |
置換 |
remove(i) / remove(e) |
削除 |
size() |
要素数 |
contains(e) |
含むか |
indexOf(e) |
インデックス検索 |
isEmpty() |
空か |
sort(comparator) |
ソート |
ArrayList vs LinkedList
| 特徴 | ArrayList | LinkedList |
|---|---|---|
| ランダムアクセス | ✅ 高速 O(1) | ❌ 低速 O(n) |
| 先頭への挿入・削除 | ❌ 低速 O(n) | ✅ 高速 O(1) |
| メモリ効率 | ✅ 良い | ❌ オーバーヘッド大 |
| 推奨度 | 通常はこちら | 特殊な場合のみ |
Set
順序なし・重複NGのコレクションです。
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
// HashSet(順序なし)
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("Java"); // 重複は無視
System.out.println(hashSet.size()); // 2
// TreeSet(ソート済み)
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
System.out.println(treeSet); // [10, 20, 30]
// 不変Set(Java 9+)
Set<String> immutable = Set.of("A", "B", "C");
集合演算
Set<Integer> a = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> b = new HashSet<>(Set.of(3, 4, 5, 6));
// 和集合
Set<Integer> union = new HashSet<>(a);
union.addAll(b); // {1, 2, 3, 4, 5, 6}
// 積集合(共通部分)
Set<Integer> intersection = new HashSet<>(a);
intersection.retainAll(b); // {3, 4}
// 差集合
Set<Integer> diff = new HashSet<>(a);
diff.removeAll(b); // {1, 2}
Map
キーと値のペアを格納するコレクションです。
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
// HashMap
Map<String, Integer> scores = new HashMap<>();
scores.put("太郎", 85);
scores.put("花子", 92);
scores.put("次郎", 78);
// アクセス
scores.get("太郎") // 85
scores.getOrDefault("美咲", 0) // 0(キーがない場合のデフォルト)
scores.containsKey("花子") // true
scores.size() // 3
// 更新・削除
scores.put("太郎", 90); // 上書き
scores.remove("次郎"); // 削除
// ループ
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.printf("%s: %d点%n", entry.getKey(), entry.getValue());
}
// キーだけ / 値だけ
scores.keySet() // [太郎, 花子]
scores.values() // [90, 92]
不変Map(Java 9+)
Map<String, Integer> map = Map.of(
"one", 1,
"two", 2,
"three", 3
);
便利メソッド
// putIfAbsent: キーが存在しない場合のみ追加
scores.putIfAbsent("美咲", 88);
// computeIfAbsent: キーが存在しない場合に計算して追加
Map<String, List<String>> groups = new HashMap<>();
groups.computeIfAbsent("A", k -> new ArrayList<>()).add("太郎");
// merge: 値のマージ
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
wordCount.merge(word, 1, Integer::sum);
}
ジェネリクス
型をパラメータ化して、型安全なコードを書けます。
// ジェネリクスなし(危険)
List rawList = new ArrayList();
rawList.add("Hello");
rawList.add(123);
String s = (String) rawList.get(1); // ClassCastException!
// ジェネリクスあり(安全)
List<String> safeList = new ArrayList<>();
safeList.add("Hello");
// safeList.add(123); // ❌ コンパイルエラー
String s = safeList.get(0); // キャスト不要
ジェネリッククラス
class Pair<A, B> {
private final A first;
private final B second;
Pair(A first, B second) {
this.first = first;
this.second = second;
}
A getFirst() { return first; }
B getSecond() { return second; }
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
// 使用
Pair<String, Integer> nameAge = new Pair<>("太郎", 25);
System.out.println(nameAge); // (太郎, 25)
ジェネリックメソッド
static <T> T getFirst(List<T> list) {
if (list.isEmpty()) {
throw new IllegalArgumentException("リストが空です");
}
return list.get(0);
}
// 使用
String first = getFirst(List.of("A", "B", "C")); // "A"
Integer num = getFirst(List.of(1, 2, 3)); // 1
境界型パラメータ
// T は Comparable を実装していなければならない
static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
max(3, 7) // 7
max("apple", "banana") // "banana"
Stream API の基礎
コレクションのデータ処理を宣言的に書けます。
import java.util.List;
import java.util.stream.Collectors;
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// フィルタ + 変換 + 集約
List<Integer> evenDoubled = numbers.stream()
.filter(n -> n % 2 == 0) // 偶数を抽出
.map(n -> n * 2) // 2倍にする
.toList(); // リストに変換
// [4, 8, 12, 16, 20]
// 合計
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum(); // 55
// 文字列結合
List<String> names = List.of("太郎", "花子", "次郎");
String joined = names.stream()
.collect(Collectors.joining(", "));
// "太郎, 花子, 次郎"
flowchart LR
Source["ソース<br>[1,2,3,4,5]"]
Filter["filter<br>偶数のみ"]
Map["map<br>2倍"]
Collect["toList<br>リスト化"]
Source --> Filter --> Map --> Collect
style Source fill:#3b82f6,color:#fff
style Filter fill:#f59e0b,color:#fff
style Map fill:#22c55e,color:#fff
style Collect fill:#8b5cf6,color:#fff
よく使うStream操作
| 操作 | 種類 | 説明 |
|---|---|---|
filter() |
中間 | 条件でフィルタ |
map() |
中間 | 要素を変換 |
sorted() |
中間 | ソート |
distinct() |
中間 | 重複除去 |
limit() |
中間 | 件数制限 |
toList() |
終端 | リストに変換 |
forEach() |
終端 | 各要素に処理 |
count() |
終端 | 件数 |
reduce() |
終端 | 集約 |
実践: 生徒管理システム
import java.util.*;
import java.util.stream.Collectors;
record Student(String name, int score, String subject) {}
public class StudentManager {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("太郎", 85, "数学"),
new Student("花子", 92, "英語"),
new Student("次郎", 78, "数学"),
new Student("美咲", 95, "英語"),
new Student("健太", 67, "数学"),
new Student("さくら", 88, "英語")
);
// 平均点
double avg = students.stream()
.mapToInt(Student::score)
.average()
.orElse(0);
System.out.printf("全体平均: %.1f点%n", avg);
// 科目別平均
Map<String, Double> subjectAvg = students.stream()
.collect(Collectors.groupingBy(
Student::subject,
Collectors.averagingInt(Student::score)
));
subjectAvg.forEach((subject, average) ->
System.out.printf("%s平均: %.1f点%n", subject, average));
// 成績上位3名
System.out.println("\n=== 成績上位3名 ===");
students.stream()
.sorted(Comparator.comparingInt(Student::score).reversed())
.limit(3)
.forEach(s -> System.out.printf("%s: %d点%n", s.name(), s.score()));
// 80点以上の名前リスト
List<String> highScorers = students.stream()
.filter(s -> s.score() >= 80)
.map(Student::name)
.toList();
System.out.println("\n80点以上: " + highScorers);
// 科目別グループ化
Map<String, List<Student>> bySubject = students.stream()
.collect(Collectors.groupingBy(Student::subject));
bySubject.forEach((subject, list) -> {
System.out.printf("\n%s: ", subject);
list.forEach(s -> System.out.printf("%s(%d) ", s.name(), s.score()));
});
}
}
まとめ
| 概念 | 説明 |
|---|---|
List |
順序あり・重複OK |
Set |
順序なし・重複NG |
Map |
キーと値のペア |
ArrayList |
最も一般的なList実装 |
HashMap |
最も一般的なMap実装 |
| ジェネリクス | 型の安全性を保証 |
| Stream API | 宣言的なデータ処理 |
重要ポイント
- 通常は**
ArrayListとHashMap**を使う - 重複を排除したい場合は**
Set** - ジェネリクスで型安全なコードを書く
- Stream APIで宣言的にデータを処理する
練習問題
問題1: 基本
Map<String, Integer>で単語の出現回数をカウントするプログラムを作成してください。
問題2: 応用
Stream APIを使って、整数リストから偶数だけを抽出し、降順にソートし、先頭5件を取得してください。
チャレンジ問題
ジェネリッククラスStack<T>を実装してください。push, pop, peek, isEmpty, size メソッドを持ち、内部ではArrayListを使いましょう。
参考リンク
次回予告: Day 10では「総合プロジェクト」に取り組みます。これまで学んだすべてのJava技術を使って、タスク管理アプリケーションを構築しましょう!