レスポンスデータを加工する（Stream）

※前回記事[レスポンスデータを加工する（拡張for文）](https://qiita.com/kilalabu/items/a1879315d292647cfcb5)のStream版です。
違いだけを知りたい人は「データを加工」までジャンプしてください。

## はじめに
レスポンスデータって全要素使うわけではなかったり、ネスト構造になってたり、結構見にくいことが多いと思います。
ということで取得したレスポンスを加工(フィルター, マッピング, ソート)して、自分好みのデータクラスを作成してみました。

## ゴール
フィルター（抽出）：likeCountが5より大きいもの
マッピング（結合）：title,body,likeCount,name
ソート（並び替え）：likeCountで降順

|加工前|加工後|
|----|----|
|![スクリーンショット 2021-03-16 22.02.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/f5eee0bb-25e2-cbf1-addf-6f8de6535538.png)|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670405/95e30cc0-cae8-4904-887a-3077ba2d8e46.png" width=100%>|


## 実装
※コード長くなるため、API使わずにレスポンスを模した自作の簡易データを使ってます。
### 加工前データクラス定義
使用しないゲッター、セッターは省略してます

```java:ArticleResponse.java
public class ArticleResponse {
    private String title;
    private String body;
    private String createdAt;
    private int likeCount;
    private User user;

    public ArticleResponse(String title, String body, String createdAt, int likeCount, User user) {
        this.title = title;
        this.body = body;
        this.createdAt = createdAt;
        this.likeCount = likeCount;
        this.user = user;
    }

    public String getTitle() {
        return title;
    }

    public String getBody() {
        return body;
    }

    public int getLikeCount() {
        return likeCount;
    }

    public User getUser() {
        return user;
    }
}
```

```java:User.java
public class User {
    private String name;
    private int id;
    private int followeesCount;
    private int followersCount;

    public User(String name, int id, int followeesCount, int followersCount) {
        this.name = name;
        this.id = id;
        this.followeesCount = followeesCount;
        this.followersCount = followersCount;
    }

    public String getName() {
        return name;
    }
}
```
### 加工データクラス定義
使用しないゲッター、セッターは省略してます

```java:ArticleBusinessModel.java
public class ArticleBusinessModel {
    private String title;
    private String body;
    private int likeCount;
    private String name;


    public ArticleBusinessModel(String title, String body, int likeCount, String name) {
        this.title = title;
        this.body = body;
        this.likeCount = likeCount;
        this.name = name;
    }

    public int getLikeCount() {
        return likeCount;
    }
}
```
### android:minSdkVersionの設定
バージョン24以上でないとStreamが使えないため

```xml:AndroidManifest.xml
<uses-sdk android:minSdkVersion="24" />
```

### データを加工
```java:ArticleRepository.java
public class ArticleRepository {
    
    private ArrayList<ArticleResponse> responses = sampleResponseData();  // 加工前のサンプルデータ

    public List<ArticleBusinessModel> mapping() {
        List<ArticleBusinessModel> processedData = responses.stream()
                .filter(article -> article.getLikeCount() > 5) 
                .map(article -> new ArticleBusinessModel(
                        article.getTitle(),
                        article.getBody(),
                        article.getLikeCount(),
                        article.getUser().getName())
                )
                .sorted(Comparator.comparing(ArticleBusinessModel::getLikeCount).reversed())
                .collect(Collectors.toList());

        // 中身見るためにJsonに変換
        Gson gson = new Gson();
        Log.i("元データ：", gson.toJson(responses));
        Log.i("加工データ：", gson.toJson(processedData));

        return processedData;
    }

    // サンプルデータ作成
    private ArrayList<ArticleResponse> sampleResponseData() {
        ArrayList<ArticleResponse> dataList = new ArrayList<>();

        User user1 = new User("Ichiro", 1, 70, 50);
        User user2 = new User("Jiro", 2, 40, 10);
        User user3 = new User("Saburo", 3, 20, 5);
        User user4 = new User("Shiro", 4, 10, 5);

        dataList.add(new ArticleResponse("Java", "静的型付け言語です", "2021/3/1", 10, user1));
        dataList.add(new ArticleResponse("Ruby", "動的型付け言語です", "2021/3/2", 8, user2));
        dataList.add(new ArticleResponse("Swift", "静的型付け言語です", "2021/3/2",14, user3));
        dataList.add(new ArticleResponse("NoCode", "ノーコードです", "2021/3/3", 3, user4));

        return dataList;
    }
}
```
※データ加工の呼び出し処理は割愛。ViewModelとかで呼び出してみてください。

## コード解説

### Streamの使い方
stream,中間操作,終端操作の順でデータを加工していく。

```xml:具体的な操作の流れ
[ stream ] 加工したいデータをStream化する
　　　　・ stream()
        Stream型に変換する
　　↓ 
[ 中間操作 ] データを加工する
　　　　・ filter（）
　　　　　指定された条件を満たすもののみを抽出する

　　　　・ map（）
　　　　　Streamの各要素を別の要素に型変換する

　　　　・ sorted（）
　　　　　指定した要素を基準にして並び替える
　　↓
[ 終端操作 ] 結果を得る
　　　　・ collect()
```
### Stream処理の解説
```java
    // memo: private ArrayList<ArticleResponse> responses = sampleResponseData();  
    
    // [ stream ]
    // List<ArticleResponse>　→　Stream<ArticleResponse> に変換
    List<ArticleBusinessModel> processedData = responses.stream()

       // [ 中間操作 ] 
       // ArticleResponseのStream内の各要素から取り出したlikeCountの中から、
       // 条件に合うものだけを抽出
       .filter(article -> article.getLikeCount() > 5)  // article(任意名)の型名を省略できる

       // ArticleResponseのStream内の各要素から必要な値を取り出して、
       // Stream<ArticleResponse>　→　Stream<ArticleBusinessModel> に変換
       .map(article -> new ArticleBusinessModel(
             article.getTitle(),
             article.getBody(),
             article.getLikeCount(),
             article.getUser().getName())
        )
        
       // ArticleBusinessModelのStream内の各要素から取り出したlikeCountをもとに並び替え
       .sorted(Comparator.comparing(ArticleBusinessModel::getLikeCount).reversed())

       // [ 終端操作 ] 
       // Stream<ArticleBusinessModel>　→　List<ArticleBusinessModel> に変換     
       .collect(Collectors.toList());
```
### 【比較してみる】 Streamを使わない場合
```java
    List<ArticleBusinessModel> processedData = new ArrayList<ArticleBusinessModel>();
    for (ArticleResponse article : responses) {
        if (article.getLikeCount() > 5) {
            processedData.add(new ArticleBusinessModel(
                    article.getTitle(),
                    article.getBody(),
                    article.getLikeCount(),
                    article.getUser().getName())
            );
        }
    }
    processedData.sort(Comparator.comparing(ArticleBusinessModel::getLikeCount).reversed());
```
### 【比較してみる】 Streamを使った場合
```java
    List<ArticleBusinessModel> processedData = responses.stream()
            .filter(article -> article.getLikeCount() > 5)
            .map(article -> new ArticleBusinessModel(
                    article.getTitle(),
                    article.getBody(),
                    article.getLikeCount(),
                    article.getUser().getName())
            )
            .sorted(Comparator.comparing(ArticleBusinessModel::getLikeCount).reversed())
            .collect(Collectors.toList());
```
Streamを使ったほうがスマート！！

## 参考URL
https://qiita.com/pebblip/items/76a5ecac09335770154b
https://qiita.com/munieru_jp/items/6c0dbada463e00429fd1
https://www.unitrust.co.jp/8367
http://enterprisegeeks.hatenablog.com/entry/2015/11/30/081118

非常にわかりやすかったです。ありがとうございました！