Java | 関数型を利用して可読性を上げる

Java

Javaでは関数型プログラミングが可能になっており、うまく利用すれば可読性も上がるしパフォーマンスも改善できます。

  • 可読性
    • ループ処理が消えること
    • メソッドチェーンが利用できること
  • パフォーマンス
    • パラレルストリーム
      • シーケンシャルな処理をスレッドプールによる並列化

今回はSpringBootを率いて可読性に着目して関数型プログラミングを行ってみます。

今回のコードは以下のfunctionalパッケージが該当します

spring-boot-memomemo/src/main/java/com/example/zanzan/functional at main · jirentaicho/spring-boot-memomemo
ただのメモです. Contribute to jirentaicho/spring-boot-memomemo development by creating an account on GitHub.

関数型を利用しないパターン

命令型の記載で書いていきます。

今回のケースはMailの取得をするという処理です
ただしメールのタイトルが空になっているものは取得の対象外とします
このようなケースはSQL側で空のタイトルを取得しないなどの対応もできますが、今回はApplication側でその制御をやっていきます

以下はメールクラスです

@AllArgsConstructor
@Getter
public class Mail {
    private final String sender;
    private final String title;

    public boolean isBlankMail(){
        return title.isBlank();
    }

    @Override
    public String toString(){
        return "送信者 : " + sender + " タイトル : " + title;
    }
}

データベースから取得する時はメールエンティティクラスを利用します

@Entity
@Table(name="mails")
public class MailEntity {

    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public int id;

    @Column(name = "sender")
    public String sender;

    @Column(name ="title")
    public String title;
}

DBから取得したMailEntityをMailクラスに変換するためのマッパークラスを用意します

@Component
public class MailMapper {

    public Mail toMail(MailEntity mailEntity){
        return new Mail(mailEntity.sender,mailEntity.title);
    }
}

リポジトリから取得してタイトルが空のメール以外のメールリストを返すサービスクラスは以下のようになります

@Component
public class MailServiceImpl implements MailService{

    @Autowired
    private MailRepository mailRepository;

    @Autowired
    private MailMapper mailMapper;

    @Override
    public List<Mail> getActiveMail() {
        List<MailEntity> mailEntities = mailRepository.getAllMail();
        List<Mail> mails = new ArrayList<>();
        for(MailEntity entity : mailEntities){
            mails.add(this.mailMapper.toMail(entity));
        }
        List<Mail> activeMails = new ArrayList<>();
        for(Mail mail : mails){
            if(mail.isBlankMail()){
                continue;
            }
            activeMails.add(mail);
        }
        return activeMails;
    }

}

見てわかる通りループを多用しているのと、全てのメールが入ったリストと、空メールを弾いたメールが入ったリストの作成が行われています。
これは単純な処理なのですが、コードを読まないと理解はできません。

コントローラーにて結果を返してあげますが、ここではMailを画面表示用のMailDtoに変換してあげます

@RestController
public class FunctionalController {

    @Autowired
    private MailService mailService;

    @Autowired
    private MailDtoMapper mailDtoMapper;

    @GetMapping("/normal/get")
    public List<MailDto> getActiveEmail(){
        // isBlankではない全てのメールを取得します
        List<Mail> mails = this.mailService.getActiveMail();
        List<MailDto> mailDtos = new ArrayList<>();
        for(Mail mail : mails){
            mailDtos.add(this.mailDtoMapper.toDto(mail));
        }
        return mailDtos;
    }
}

コントローラーでもループ処理が行われています

関数型を利用したパターン

次に関数型を利用したパターンです

まずはサービスクラスを関数型で書き換えてみます

@Component
public class MailServiceImpl implements MailService{

    @Autowired
    private MailRepository mailRepository;

    @Autowired
    private MailMapper mailMapper;

    @Override
    public List<Mail> getActiveMailFunctional() {
        return mailRepository.getAllMail().stream()
                .map(mailMapper::toMail)
                .filter(Predicate.not(Mail::isBlankMail))
                .collect(Collectors.toList());
    }
}

やっていることは同じですがループは消え去り、コードを読まなくても何をやっているのか理解できるようになっています。

filter(Predicate.not(Mail::isBlankMail))は一致しない要素をfilterしています

次にコントローラーを書きます

@RestController
public class FunctionalController {

    @Autowired
    private MailService mailService;

    @Autowired
    private MailDtoMapper mailDtoMapper;

    @GetMapping("/functional/get")
    public List<MailDto> getActiveEmailFunctional(){
        // isBlankではない全てのメールを取得します
        return this.mailService.getActiveMailFunctional().stream()
                .map(mailDtoMapper::toDto)
                .collect(Collectors.toList());
    }
}

もともとコードが少なったかですが、ループが消えただけでもスッキリ感がアップします。

ちなみに、データベースには以下のようなレコードが入っています

結果

functional/get

normal/get

関数型とオブジェクト指向のガチ両刀になると、更にコードがパワーアップしますね。

コメント

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