「map 操作」をざっくりイメージする
Stream の map は、一言でいうと
「流れてくる要素を、別の値に変換して流し直す 操作」
です。
元の要素数は変えない
1 個入ったら、必ず 1 個出てくる
「中身の型」や「値の形」を変える
この 3 点が本質です。
for 文でいうと、
List<String> upper = new ArrayList<>();
for (String s : list) {
upper.add(s.toUpperCase());
}
Javaこの「s を s.toUpperCase() に変えて新しいリストに追加する」部分が、
丸ごと map だと思ってください。
一番基本:文字列を大文字に変換する
for 文バージョン
まずはおなじみの書き方から。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MapBasicFor {
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "carol");
List<String> upper = new ArrayList<>();
for (String name : names) {
upper.add(name.toUpperCase());
}
System.out.println(upper); // [ALICE, BOB, CAROL]
}
}
Javaやっていることは、「1 つずつ取り出して、変えて、別リストに入れる」。
Stream + map バージョン
同じことを Stream で書くとこうなります。
import java.util.Arrays;
import java.util.List;
public class MapBasicStream {
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "carol");
List<String> upper =
names.stream()
.map(name -> name.toUpperCase())
.toList(); // Java 16 以降
System.out.println(upper); // [ALICE, BOB, CAROL]
}
}
Java日本語にすると、
names をストリームにして
要素(name)を name.toUpperCase() に変換して流し直して
最後に List に集める
という 3 ステップです。
ここで重要なのは、「元の件数は 3 件 → 結果も 3 件」のままという点です。
増えも減りもしません。ここが filter との違いです。
map の型変換を意識してみる
String から Integer に変換する
map は「型を変える」のにとても相性が良いです。
例えば、「数字の文字列リスト」を「整数のリスト」に変えたい場合。
import java.util.Arrays;
import java.util.List;
public class MapTypeChange {
public static void main(String[] args) {
List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints =
numbers.stream()
.map(s -> Integer.parseInt(s))
.toList();
System.out.println(ints); // [1, 2, 3]
}
}
JavaStream<String> が map を通ることで、Stream<Integer> に変わっています。
「文字列 → 別の型」
「DTO → エンティティ」
「APIレスポンス → 画面用モデル」
など、型を変えたい場面では、全部 map が使えると考えて OK です。
自作クラスに変換する
少しだけ踏み込んで、自作クラスへの変換を見てみます。
class User {
String name;
User(String name) { this.name = name; }
@Override
public String toString() { return name; }
}
Java文字列リストから User リストへ。
import java.util.Arrays;
import java.util.List;
public class MapToCustomClass {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob");
List<User> users =
names.stream()
.map(name -> new User(name))
.toList();
System.out.println(users); // [Alice, Bob]
}
}
Java「T を受け取って R を返す」変換なら、なんでも map で書ける、という感覚を持っておくと強いです。
map / mapToInt / mapToLong / mapToDouble の違い
参照型のまま変換するとき:map
これまで出てきたのは全部 map(汎用版)でした。
Stream<String> → map(…) → Stream<Integer>
Javaのように、「参照型 → 参照型」の変換には map を使います。
数値ストリームに変換するとき:mapToInt など
合計や平均を取りたいときには、IntStream などの「プリミティブ専用ストリーム」が便利です。
import java.util.Arrays;
public class MapToIntExample {
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Carol"};
int totalLength =
Arrays.stream(names) // Stream<String>
.mapToInt(String::length) // IntStream
.sum(); // 長さの合計
System.out.println(totalLength);
}
}
Javaここでの流れは、
Stream<String> から mapToInt で IntStream を生成IntStream の sum で合計
です。
mapToInt の引数には「T をもらって、int を返す関数」を渡します。String::length は「String を受け取って int を返すメソッド」なので、ぴったりはまっています。
「最終的に sum / average / max / min を取りたい」
→ 一旦 mapToInt / mapToLong / mapToDouble にしておくと、後続の操作が楽になる
という感覚を持っておいてください。
filter と map の違い・組み合わせ方
filter は「間引く」、map は「変える」
直感的な対比で整理します。
filter
…… 条件を満たさないものを捨てる(要素数が減る)
map
…… 要素を別の値に変換する(要素数は変わらない)
例えば、「偶数だけ」「2 倍して」合計する例。
import java.util.Arrays;
import java.util.List;
public class FilterMapExample {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum =
list.stream()
.filter(n -> n % 2 == 0) // 偶数だけ残す(1,3,5 は消える)
.mapToInt(n -> n * 2) // 残った 2,4 を 4,8 に変える
.sum(); // 4 + 8 = 12
System.out.println(sum);
}
}
Java流れを丁寧に追うと:
元のストリーム: 1, 2, 3, 4, 5
filter 後: 2, 4
mapToInt 後: 4, 8
sum: 12
というパイプラインになっています。
filter と map の組み合わせは、Stream の中でも最頻出です。
「まず絞り込んでから、形を変える」
これが鉄板パターンだと思ってください。
map で「副作用」を書き始めると危険、という話
やりがちなアンチパターン
初心者が最初につまづきやすいのが、「map の中で何でもやろうとする」パターンです。
例えば、こんなコードはあまり良くありません。
List<String> result = new ArrayList<>();
names.stream()
.map(name -> {
String upper = name.toUpperCase();
result.add(upper); // 外側のリストに add(副作用)
return upper;
})
.forEach(System.out::println);
Java技術的には動きますが、
map は「変換」だけを担当してほしい
外部の変数(result)を書き換えるのは、読みづらさ・バグの元になる
という意味で、あまり良いスタイルではありません。
素直に「map して collect」する
同じことをしたいなら、素直にこう書いた方がいいです。
List<String> result =
names.stream()
.map(String::toUpperCase)
.toList();
Java役割がはっきり分かれています。
map … 変換だけ
collect/toList … 集めるだけ
「1 ステップ 1 役割」を意識すると、Stream のコードはぐっと読みやすくなります。
まとめ:map 操作を自分の中でどう位置づけるか
初心者向けに map の本質をまとめると、こうです。
流れてくる要素を、1 対 1 で別の値に変換する 操作
要素数は変えないけれど、「中身」や「型」を変える
「for で取り出して、新しいリストに add する処理」をまとめて表現したものmap(参照型)と mapToInt / mapToLong / mapToDouble(数値用)を使い分ける
特に大事なのは、
filter は「間引き」、map は「変換」
この 2 つを組み合わせて、「絞る → 変える → 集める」という流れを作る
という感覚です。
