StreamAPIのmap関数についての理解

# 【Java】StreamAPIのmap()メソッドを完全理解！初心者向け徹底解説

## はじめに

JavaのStreamAPIは、コレクションの操作を直感的かつ効率的に行える強力な機能です。その中でも`map()`メソッドは最も頻繁に使用される操作の一つです。

この記事では、`map()`メソッドの基本的な使い方から実践的な活用例まで、初心者にもわかりやすく解説していきます。

## map()メソッドとは？

`map()`メソッドは、**ストリームの各要素に対して指定した変換処理を適用し、新しいストリームを生成する**メソッドです。

簡単に言うと：
- 元のデータを別の形に変換したい時に使用
- 一対一の変換処理（1つの入力に対して1つの出力）
- 元のストリームは変更されず、新しいストリームが作成される

## 基本的な構文

```java
Stream<R> map(Function<? super T, ? extends R> mapper)
```

- `T`: 入力の型
- `R`: 出力の型
- `mapper`: 変換処理を定義する関数

## 実際のコード例で理解しよう

### 例1: 文字列を大文字に変換

```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("alice", "bob", "charlie");
        
        // 従来の書き方（for文）
        List<String> upperNamesOld = new ArrayList<>();
        for (String name : names) {
            upperNamesOld.add(name.toUpperCase());
        }
        
        // Stream APIのmap()を使用
        List<String> upperNames = names.stream()
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println(upperNames); // [ALICE, BOB, CHARLIE]
    }
}
```

### 例2: 数値を2倍にする

```java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> doubledNumbers = numbers.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());

System.out.println(doubledNumbers); // [2, 4, 6, 8, 10]
```

### 例3: オブジェクトのプロパティを取得

```java
class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
}

public class PersonExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("田中", 25),
            new Person("佐藤", 30),
            new Person("鈴木", 35)
        );
        
        // 名前だけを取得
        List<String> names = people.stream()
            .map(Person::getName)
            .collect(Collectors.toList());
        
        // 年齢だけを取得
        List<Integer> ages = people.stream()
            .map(Person::getAge)
            .collect(Collectors.toList());
        
        System.out.println(names); // [田中, 佐藤, 鈴木]
        System.out.println(ages);  // [25, 30, 35]
    }
}
```

## ラムダ式とメソッド参照の使い分け

### ラムダ式の場合

```java
// 複雑な変換処理
List<String> formattedNames = people.stream()
    .map(person -> person.getName() + "さん（" + person.getAge() + "歳）")
    .collect(Collectors.toList());
```

### メソッド参照の場合

```java
// シンプルな変換処理
List<String> names = people.stream()
    .map(Person::getName)  // メソッド参照の方がスッキリ
    .collect(Collectors.toList());
```

## 実践的な活用例

### 例1: CSVデータの変換

```java
List<String> csvLines = Arrays.asList(
    "田中,25,東京",
    "佐藤,30,大阪",
    "鈴木,35,名古屋"
);

List<Person> people = csvLines.stream()
    .map(line -> {
        String[] parts = line.split(",");
        return new Person(parts[0], Integer.parseInt(parts[1]));
    })
    .collect(Collectors.toList());
```

### 例2: APIレスポンスの変換

```java
class ApiResponse {
    private String id;
    private String title;
    private boolean active;
    
    // コンストラクタ、ゲッター等省略
}

class DisplayItem {
    private String displayTitle;
    private String status;
    
    public DisplayItem(String title, boolean active) {
        this.displayTitle = title.toUpperCase();
        this.status = active ? "有効" : "無効";
    }
    
    // ゲッター等省略
}

// APIレスポンスを表示用オブジェクトに変換
List<ApiResponse> responses = getApiResponses();
List<DisplayItem> displayItems = responses.stream()
    .map(response -> new DisplayItem(response.getTitle(), response.isActive()))
    .collect(Collectors.toList());
```

## map()と他のStream操作の組み合わせ

```java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<String> result = numbers.stream()
    .filter(n -> n % 2 == 0)        // 偶数のみフィルタ
    .map(n -> n * n)                // 二乗
    .map(n -> "数値: " + n)         // 文字列に変換
    .collect(Collectors.toList());

System.out.println(result); // [数値: 4, 数値: 16, 数値: 36, 数値: 64, 数値: 100]
```

## よくある間違いとその対処法

### 間違い1: nullチェックを忘れる

```java
// 危険な例
List<String> names = people.stream()
    .map(Person::getName)  // nameがnullの場合NullPointerException
    .collect(Collectors.toList());

// 安全な例
List<String> names = people.stream()
    .map(person -> person.getName() != null ? person.getName() : "名前なし")
    .collect(Collectors.toList());

// Optional使用例
List<String> names = people.stream()
    .map(Person::getName)
    .map(name -> Optional.ofNullable(name).orElse("名前なし"))
    .collect(Collectors.toList());
```

### 間違い2: mapとflatMapの使い分け

```java
// mapの場合（一対一変換）
List<String> words = Arrays.asList("hello", "world");
List<Integer> lengths = words.stream()
    .map(String::length)  // 各文字列の長さを取得
    .collect(Collectors.toList());

// flatMapの場合（一対多変換）
List<String> sentences = Arrays.asList("hello world", "java stream");
List<String> allWords = sentences.stream()
    .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
    .collect(Collectors.toList());
```

## パフォーマンスの考慮事項

### 1. 並列処理の活用

```java
// 大量のデータを処理する場合
List<Integer> largeList = IntStream.range(1, 1000000)
    .boxed()
    .collect(Collectors.toList());

List<Integer> result = largeList.parallelStream()  // 並列処理
    .map(n -> n * n)
    .collect(Collectors.toList());
```

### 2. 中間操作の最適化

```java
// 効率的な書き方
List<String> result = people.stream()
    .filter(person -> person.getAge() >= 20)  // filterを先に
    .map(Person::getName)                     // mapは後に
    .collect(Collectors.toList());
```

## まとめ

`map()`メソッドは以下のような特徴があります：

- **一対一変換**: 1つの入力に対して1つの出力
- **型変換可能**: 異なる型に変換できる
- **関数型**: ラムダ式やメソッド参照が使用可能
- **遅延評価**: 終端操作が呼ばれるまで実行されない

### 使用する場面
- データの形式を変換したい時
- オブジェクトのプロパティを抽出したい時
- 計算結果を新しいリストにしたい時

### 注意点
- nullチェックを忘れずに
- flatMapとの使い分けを理解する
- パフォーマンスを考慮した書き方を心がける

Stream APIの`map()`メソッドをマスターすることで、より読みやすく保守性の高いJavaコードが書けるようになります。まずは簡単な例から始めて、徐々に複雑な処理にチャレンジしてみてください！