Java | Java 標準ライブラリ:map 操作

Java Java
スポンサーリンク

「map 操作」をざっくりイメージする

Stream の map は、一言でいうと

「流れてくる要素を、別の値に変換して流し直す 操作」

です。

元の要素数は変えない
1 個入ったら、必ず 1 個出てくる
「中身の型」や「値の形」を変える

この 3 点が本質です。

for 文でいうと、

List<String> upper = new ArrayList<>();
for (String s : list) {
    upper.add(s.toUpperCase());
}
Java

この「ss.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]
    }
}
Java

Stream<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> から mapToIntIntStream を生成
IntStreamsum で合計

です。

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 つを組み合わせて、「絞る → 変える → 集める」という流れを作る

という感覚です。

タイトルとURLをコピーしました