Stream APIって便利なんですが「さて、どう書くんだっけ?」ってなることありますよね。
特にListとMapを行き来する処理は、一度覚えてしまえばあとは応用するだけなのですが久しぶりに書くと手が止まりがちです。何なら毎回検索してる気がする。
このメモを3歩歩いても忘れる筆者のために残しておきます。同じく「あれ、どう書くんだっけ」となった方の助け舟にもなれば。
この記事でわかること
Collectors.toMap()を使ってListからMapへ変換する方法keySet()やStreamを使ってMapのキーをListへ変換する方法filter/map/flatMapなどよく使うStream処理のパターンCollectorsの早見表- Java 8〜17での書き方の違いと注意点
for文 vs Stream
| 比較 | Stream API | for文 |
|---|---|---|
| 可読性 | ◎ 高い(※1) | 〇 普通 |
| 大量データ処理 | ◎ 得意 | 〇 普通 |
| デバッグ | △ やや難しい | 〇 簡単 |
| 学習コスト | △ やや高い | 〇 低い |
| 並列処理 | 〇 可能 | × 難しい |
※1 とはいえ無理に1行にする、ネストしすぎると可読性、保守性が落ちます。
Stream APIの基本的な流れ
まず全体のイメージを掴んでおきましょう。StreamはCollectionや配列をパイプライン的に処理する仕組みで、3段階で動きます。
- ソース —
list.stream()でStreamを生成する。 - 中間操作 —
filter()/map()/sorted()など、処理を繋げる(実際にはまだ動かない)。 - 終端操作 —
collect()/count()/findFirst()など、これを呼んで初めて処理が走る。
「中間操作は遅延評価される(実際には終端操作が来るまで動かない)」というのがStreamの特徴です。これを知っておくと、パフォーマンスのイメージが掴みやすくなりますよ。
ListからMapへ変換する
基本の書き方
Collectors.toMap()を使います。第1引数にキー、第2引数に値を指定するだけです。
Map<K, V> map = list
.stream()
.collect(Collectors.toMap(keyMapper, valueMapper));具体例
Userクラスのリストをidをキー・nameを値にしたMapへ変換するパターンです。
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() { return this.id; }
public String getName() { return this.name; }
}
List<User> users = Arrays.asList(
new User(1, "Alice"),
new User(2, "Bob"),
new User(3, "Charlie")
);
Map<Integer, String> userMap = users
.stream()
.collect(Collectors.toMap(User::getId, User::getName));
// 結果: {1=Alice, 2=Bob, 3=Charlie}メソッド参照(User::getIdなど)を使うとすっきり書けます。ラムダ式でu -> u.getId()と書いても同じ意味です。
重複キーがある場合
リストに同じキーになる要素が2件以上あるとIllegalStateExceptionが発生します。3番目の引数(マージ関数)を渡すことで対処できます。
Map<Integer, String> userMap = users
.stream()
.collect(Collectors.toMap(
User::getId,
User::getName,
(existing, replacement) -> existing // 既存の値を優先する場合
));⚠️ 注意: valueに
nullが入るとNullPointerExceptionになります。nullを含む可能性があるリストはあらかじめfilterでnullを弾いておくか、後述のHashMap::newを使う方法に切り替えるのが無難です。
MapのキーをListへ変換する
keySet()をそのまま使う
一番シンプルな方法です。変換だけしたい場合はこれで十分です。
Map<Integer, String> map = Map.of(1, "Alice", 2, "Bob", 3, "Charlie");
List<Integer> keyList = new ArrayList<>(map.keySet());Map.of()はJava 9以降で使えます。Java 8環境では使えないので注意が必要です。Java 8ではnew HashMap<>()に値を個別にput()するか、後述のStreamで組み立てる形になります。
Streamを使う
Streamを挟むとソートや絞り込みと組み合わせやすくなります。
// Listに変換するだけ
List<Integer> keyList = map
.keySet()
.stream()
.collect(Collectors.toList());
// ソートして取り出す
List<Integer> sortedKeys = map
.keySet()
.stream()
.sorted()
.collect(Collectors.toList());💡 補足:
Map.keySet()が返すSetは順序を保証しません。挿入順を保ちたい場合はLinkedHashMapを使うかsorted()を挟むと判断できるわけですね。
よく使うStream処理のパターン
filter — 条件を絞り込む
条件に合う要素だけを残したいときに使います。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evens = numbers
.stream()
.filter(n -> (n % 2) == 0)
.collect(Collectors.toList());
// 結果: [2, 4, 6]Userリストから名前で絞り込む場合もよくあるパターンです。
List<User> filtered = users
.stream()
.filter(u -> u.getName().startsWith("A"))
.collect(Collectors.toList());
// 結果: Alice だけ残るmap — 要素を変換する
Streamのmapは「各要素を別の値に変換する」操作です。コレクションのMap(Key-Value型)とは別物なので最初は少し紛らわしいですが、役割はまったく違うと考えるとわかりやすいです。
List<String> names = List.of("alice", "bob", "charlie");
List<String> upper = names
.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 結果: [ALICE, BOB, CHARLIE]数値に変換して合計を出したい場合はmapToInt()が便利です。
int totalId = users
.stream()
.mapToInt(User::getId)
.sum();Streamの条件式や変換処理を追いかけるなら、Eclipseの条件付きブレークポイントも覚えておくと便利です。

flatMap — ネストしたListを平坦化する
リストのリスト(ネスト構造)を1つのリストにまとめたいときに使います。
List<List<Integer>> nested = List.of(
List.of(1, 2, 3),
List.of(4, 5, 6)
);
List<Integer> flat = nested
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 結果: [1, 2, 3, 4, 5, 6]mapだとListのListのままになってしまいますがflatMapを使うと1段階平坦化できます。
distinct / sorted — 重複除去・ソート
List<Integer> nums = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5);
List<Integer> result = nums
.stream()
.distinct() // 重複を除く
.sorted() // 昇順にソート
.collect(Collectors.toList());
// 結果: [1, 2, 3, 4, 5, 6, 9]降順にしたい場合はsorted(Comparator.reverseOrder())を使います。
groupingBy — グループ化してMapに変換する
toMap()は1対1のマッピングですが同じキーに複数の要素を紐づけたい(1対多)場合はgroupingByが向いています。
// 部署IDでグループ化(ここではidをキーにした例)
Map<Integer, List<User>> grouped = users
.stream()
.collect(Collectors.groupingBy(User::getId));
// グループごとの件数が欲しい場合
Map<Integer, Long> countById = users
.stream()
.collect(Collectors.groupingBy(User::getId, Collectors.counting()));toMapは「1つのキーに1つの値」、groupingByは「1つのキーに複数の値」というイメージで使い分けると迷いにくいです。
joining — 文字列を結合する
List<String> words = List.of("Java", "Stream", "API");
// 区切り文字だけ
String joined = words
.stream()
.collect(Collectors.joining(", "));
// 結果: "Java, Stream, API"
// 前置・後置を付ける場合
String wrapped = words
.stream()
.collect(Collectors.joining(", ", "[", "]"));
// 結果: "[Java, Stream, API]"count / findFirst / anyMatch
// 条件に合う件数
long count = users
.stream()
.filter(u -> u.getName().startsWith("A"))
.count();
// 条件に合う最初の1件(Optionalで返ってくる)
Optional<User> first = users
.stream()
.filter(u -> u.getId() > 1)
.findFirst();
// 条件を満たす要素が1件でもあるか
boolean hasAlice = users
.stream()
.anyMatch(u -> u.getName().equals("Alice"));
// 全件が条件を満たすか
boolean allPositive = numbers
.stream()
.allMatch(n -> n > 0);
// 条件を満たす要素が1件もないか
boolean noneNegative = numbers
.stream()
.noneMatch(n -> n < 0);findFirst()はOptional<T>を返すので.orElse(null)や.orElseThrow()などで値を取り出します。
Collectors早見表
| Collector | 主な用途 |
|---|---|
toList() | Listに変換する。 |
toUnmodifiableList() | 変更不可のListに変換する(Java 10以降)。 |
toSet() | Setに変換する(重複が自動で除去される)。 |
toMap(k, v) | Mapに変換する(1対1)。 |
groupingBy(f) | グループ化して Map<K, List<V>> に変換する(1対多)。 |
joining(区切り) | 文字列を結合する。 |
counting() | groupingBy との組み合わせでグループ内件数を集計する。 |
toUnmodifiableMap(k, v) | 変更不可のMapに変換する(Java 10以降)。 |
partitioningBy(predicate) | 条件でtrueとfalseの2グループに分ける。 |
Q&A
Java開発まわりの設定やデバッグもあわせて確認したい方は、Eclipse・Java関連の記事まとめもどうぞ。

まとめ
- ListからMapへの変換は
Collectors.toMap(keyMapper, valueMapper)が基本だよ。 - 重複キーがあるときは第3引数のマージ関数を忘れずに渡してね。
- MapのキーをListにするだけなら
new ArrayList<>(map.keySet())が一番シンプルだよ。 - Streamは中間操作(
filter/map/sortedなど)を繋げて終端操作(collect/count/findFirstなど)で結果を出す流れだよ。 groupingByは1対多のグループ化、toMapは1対1のマッピングと使い分けてね。stream.toList()(Java 16〜)は変更不可のListを返すので後から追加・削除が必要な場合はCollectors.toList()を使ってね。






