ListからMapへ変換するならCollectors.toMap()が基本です。
たとえばUser::getIdをキーUser::getNameを値にする場合はusers.stream().collect(Collectors.toMap(User::getId, User::getName))と書けます。
逆にMapのキーをListへ変換するだけならnew ArrayList<>(map.keySet())がシンプルです。この記事では、この2つを起点によく使うStream処理をまとめています。
Java 8〜17での書き方の違いと注意点
| API・書き方 | 利用できるバージョン |
|---|---|
| Arrays.asList() | Java 8で利用可能 |
| List.of()・Map.of() | Java 9以降 |
| Collectors.toUnmodifiableList()・toUnmodifiableMap() | Java 10以降 |
| Stream.toList() | Java 16以降 |
Java 8が今も更新される理由やLTSごとのサポート状況はJava 8とJava 26の違いを整理した記事で確認できます。


for文 vs Stream
| 比較 | Stream API | for文 |
|---|---|---|
| 可読性 | 絞り込みや変換を連続して書く処理に向く | 単純なループや分岐が多い処理に向く |
| 実行性能 | 処理内容による。逐次Streamが常に速いわけではない | 単純処理ではオーバーヘッドを抑えやすい |
| デバッグ | パイプラインの途中状態を追いにくい | 1ステップずつ追いやすい |
| 並列化 | parallelStream()へ切り替えやすいが、必ず高速になるわけではない | ExecutorやFork/Joinなど別の仕組みが必要 |
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 // 既存の値を優先する場合
));MapのキーをListへ変換する
keySet()をそのまま使う
keySet()の反復順序は元のMap実装に従います。HashMapは順序を保証しません。挿入順を保つならLinkedHashMap、キー順に並べ替えるならStreamのsorted()を使います。
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ではnew HashMap<>()でMapを作成しput()でキーと値を追加します。
Streamを使う
Streamを挟むとソートや絞り込みと組み合わせやすくなります。
// Listに変換するだけ
List<Integer> keyList = map
.keySet()
.stream()
.collect(Collectors.toList());
// ソートして取り出す
List<Integer> sortedKeys = map
.keySet()
.stream()
.sorted()
.collect(Collectors.toList());よく使うStream処理のパターン
filter — 条件を絞り込む
条件に合う要素だけを残したいときに使います。
List<Integer> numbers = Arrays.asList(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 = Arrays.asList("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 = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(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 = Arrays.asList(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が向いています。
List<String> names = Arrays.asList("Alice", "Andrew", "Bob");
Map<Character, List<String>> grouped = names
.stream()
.collect(Collectors.groupingBy(name -> name.charAt(0)));
// 結果: {A=[Alice, Andrew], B=[Bob]}
Map<Character, Long> countByInitial = names
.stream()
.collect(Collectors.groupingBy(
name -> name.charAt(0),
Collectors.counting()
));
// 結果: {A=2, B=1}toMapは「1つのキーに1つの値」、groupingByは「1つのキーに複数の値」というイメージで使い分けると迷いにくいです。
joining — 文字列を結合する
List<String> words = Arrays.asList("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グループに分ける。 |
よくある質問
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は変更できません。追加・削除できるArrayListが必要な場合は、collect(Collectors.toCollection(ArrayList::new))を使います。







