0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java StreamのmapとtoListを、現場で使っていたfor文と対応づけて理解する

0
Posted at

JavaのコードやAIが生成したコードを見ていると、こんな処理が出てくる。

List<ProjectResponse> responses = projects.stream()
        .map(project -> new ProjectResponse(
                project.getId(),
                project.getName(),
                project.getStatus()
        ))
        .toList();

最初に見たとき、

stream()は何をしている?
map()は何を変換している?
toList()すると何が入る?

という部分が曖昧だった。

ただ、自分が実務で使っていたfor文と対応させるとかなり理解しやすかった。


現場でよく見ていたfor文

実務では、DBから取得したデータを別のオブジェクトへ詰める処理をよく見ていた。

List<TargetEntity> targetList = new ArrayList<>();

for (SourceEntity source : sourceList) {
    TargetEntity target = new TargetEntity(
        source.getId(),
        source.getName(),
        source.getStatus()
    );

    targetList.add(target);
}

処理としては、

sourceListから1件取り出す
↓
必要な値をgetterで取得する
↓
TargetEntityを作る
↓
targetListへ追加する

という流れ。


同じ処理をStreamで書く

Streamを使うと、こう書ける。

List<TargetEntity> targetList = sourceList.stream()
        .map(source -> new TargetEntity(
                source.getId(),
                source.getName(),
                source.getStatus()
        ))
        .toList();

最初は別物に見えたが、for文と対応させると分かりやすい。


stream() は処理対象にする

sourceList.stream()

は、

sourceListの要素を
Streamとして処理できる状態にする

という意味。

この時点ではまだ List<TargetEntity> ができたわけではない。

この後に map() や filter() などの処理をつなげられる。


map() は1件を別のデータへ変換する

.map(source -> new TargetEntity(
        source.getId(),
        source.getName(),
        source.getStatus()
))

これは、

SourceEntityを1件受け取る
↓
getterで必要な値を取る
↓
TargetEntityを作る

という処理。

つまり、

SourceEntity
↓
TargetEntity

への変換になる。

map() は、

1件のデータを
別のデータへ変換する

と考えると分かりやすかった。


for文の変換部分と対応している

for文では、

TargetEntity target = new TargetEntity(
    source.getId(),
    source.getName(),
    source.getStatus()
);

としていた。

Streamでは、

.map(source -> new TargetEntity(
        source.getId(),
        source.getName(),
        source.getStatus()
))

になる。

自分がfor文の中でやっていた、

1件取り出す
↓
別オブジェクトを作る

という部分が map() に対応している。


toList() するとListになる

次に分かりにくかったのが、

.toList();

だった。

map() の時点では、結果はまだListではない。

たとえば、

sourceList.stream()
        .map(source -> new TargetEntity(...))

の結果はイメージとして、

Stream<TargetEntity>

になっている。

ここで、

.toList()

とすると、

Stream<TargetEntity>
↓
List<TargetEntity>

になる。

そのため、

List<TargetEntity> targetList = sourceList.stream()
        .map(source -> new TargetEntity(...))
        .toList();

と受け取ることができる。


for文の add() と対応させる

for文では、

targetList.add(target);

として、1件ずつListへ追加していた。

Streamでは、

.toList();

によって、map() で変換されたデータをまとめてListにする。

対応させると、

for文

1件取り出す
↓
TargetEntityに変換
↓
targetList.add(target)

に対して、

Stream

stream()
↓
map(...)
↓
toList()

となる。

この対応でかなり理解しやすくなった。


なぜProjectResponseへ変換するのか

Web APIでは、DBから取得したデータをResponse用のオブジェクトへ変換することがある。

たとえば、DB側で使う Project が、

class Project {
    Long id;
    String name;
    String status;
    String internalMemo;
    LocalDateTime createdAt;
}

だったとする。

しかしAPIで返したいのは、

class ProjectResponse {
    Long id;
    String name;
    String status;
}

だけかもしれない。

そこで、

.map(project -> new ProjectResponse(
        project.getId(),
        project.getName(),
        project.getStatus()
))

と変換する。

理由は、

DBから取ってきたProjectを
そのまま外に返さず、
APIで返したい形に整えるため

だった。

単に型を変えているだけではなく、

DB内部で扱うデータ
↓
APIとして外に返すデータ

へ役割を変えている。

これによって、

内部用の項目を返さない
必要な項目だけ返す
DBの構造とAPIの構造を分ける

ことができる。


全体をもう一度見る

List<ProjectResponse> responses = projects.stream()
        .map(project -> new ProjectResponse(
                project.getId(),
                project.getName(),
                project.getStatus()
        ))
        .toList();

この処理は、

projects
DBなどから取得したProject一覧

↓

stream()
Streamとして処理する

↓

map()
ProjectをProjectResponseへ変換する

↓

toList()
変換後のProjectResponseをListにまとめる

↓

List<ProjectResponse> responses
レスポンス用の一覧として受け取る

という流れ。


まとめ

自分が現場で見慣れていたfor文は、

List<TargetEntity> targetList = new ArrayList<>();

for (SourceEntity source : sourceList) {
    TargetEntity target = new TargetEntity(...);
    targetList.add(target);
}

という形だった。

Streamでは、

List<TargetEntity> targetList = sourceList.stream()
        .map(source -> new TargetEntity(...))
        .toList();

と書ける。

対応させると、

for文で1件ずつ処理する
↓
stream()

別オブジェクトへ変換する
↓
map()

Listへ追加していく
↓
toList()

となる。

特に、

map()は「変換」
toList()は「変換結果をListにまとめる」

と理解すると読みやすかった。

そしてResponseへ変換する理由も、

DBから取ったデータを
APIで返したい形に整えるため

と考えると、単なる書き方ではなく実務での意味まで理解しやすかった。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?