ペアプロで教えた、Javaで流れるような処理を書くコツ


この記事は何?

新人向けにJavaの教育をした内容を記事に起こしました。
とあるJavaプロジェクトにて、私はペアプログラミング(教える側)をやりました。そこで実際にジュニアエンジニアの方へアドバイスした内容です。

  • OptionalやStreamで流れるような処理を書くコツ
  • IDEを活用してプログラミングを楽にするコツ

ちなみにそのプロジェクトはJava 1.8です。

対象者

Javaを仕事にしていて、まだOptionalやStreamの扱いになれていない人。

  • 新卒(新人)
  • 独学でプログラマになった未経験エンジニア
  • ジュニアエンジニア

とくに最近はRubyみたいなプログラミング言語を学んでエンジニアになった人が多いと思うので、そういった人がJavaと仲良くなるためのプラス要素になってもらえればと。

TL;DR

分解して考えよう!
一度にすべての処理を.orElse.streamで繋げようとしなくて良い。

IDEの助けを借りよう!
Javaで型を書かなくても良い。代わりにIDEに書いてもらって、別な仕事に集中しよう。

分割して考えよう!

先人が残したJavaコードは、stream、map、filter、orElseなどが数珠つなぎのようになっていてると思います。おそらく、それは良いJavaコードなので「自分もこんな風に書かなきゃいけない!」と思ってしまうと思います。しかし、Optionalやstreamの知識が浅い状態で真似しようとすると、自分が何をやっているのかわからなくなってしまいます。

そこで、 処理を分割してコードに落としていく というのをやってみてください。

例えば以下のようなコードがあるとします。(適当に私が考えたものです)

@Getter @AllArgsConstructor
public class Review {
  private final int id;
  private final int numStars;
  private final Optional<String> comment;
}

// こんな変数があるとして
final Optional<List<Review>> maybeReviews = /* 省略 */;

このmaybeReviewsから、すべてのコメントを取り出したリストを返すメソッドを作成してみます。一度にやろうとせず、問題を分割して考えてみてください。特にOptionalは扱いが最初は難しいと思うので、どうやってOptionalを外すかを意識するのがポイントです。

  1. Optionalを外してListを取り出す
  2. Streamでコメントの存在するReviewのみのリストを作成する
  3. StreamでそのリストからOptionalを外してコメントのリストを作成する
  4. 1〜3を組み合わせて1つの処理にする

1. Optionalを外してListを取り出す

Optionalを外すときに考えなきゃいけないことは、「空だった場合に代替の値をセットする」です。orElseを使って、空のリストをセットしてあげましょう。

// Optionalを外す。emptyの場合は空リスト
final List<Review> reviews = maybeReviews.orElse(Collections.emptyList());

2. Streamでコメントの存在するReviewのみのリストを作成する

.mapは引数にとった関数を適用するメソッドです。.map(Review::getComment)commentを書く要素から抽出しています。reviewsは空リストの場合もありますが、空リストの場合はこれら処理はスキップされるので、気にしなくてOKです。

final List<Optional<String>> comments = reviews
    .stream()                      // Streamスタート
    .map(Review::getComment)       // コメントを取り出す
    .collect(Collectors.toList()); // リストにして返す

3. StreamでそのリストからOptionalを外してコメントのリストを作成する

.filterを使えば、条件がtrueになった要素のみを取り出すことができます。ここでは.filter(Optional::isPresent)map(Optional::get)を組み合わせて、Optionalを外しています。

final List<String> results = comments
    .stream()                      // Streamスタート
    .filter(Optional::isPresent)   // 存在する値のみに絞る
    .map(Optional::get)            // Optionalを外す
    .collect(Collectors.toList()); // リストにして返す

4. 1〜3を組み合わせて1つの処理にする

ここまでして、やっと目的のListを生成することができました。これを一度にやれるように繋げてみてください。改行はありますが、処理をまとめることができました!maybeReviewsがemptyのときはorElseで空リストを返し、それ以外のときはコメントのリストを返します。

return maybeReviews
    .orElse(Collections.emptyList()) // emptyなら空リストを返す
    .stream()                        // streamを開始する
    .map(Review::getComment)         // commentを取り出す
    .filter(Optional::isPresent)     // 存在するかチェック
    .map(Optional::get)              // 上でチェックしたのでgetでOptionalを外す
    .collect(Collectors.toList());   // リストにして返す

最終的に、流れるような処理を書くことができました。いきなりこれを書けない場合は、処理を1つ1つ分解して、後から繋げて書いてみてください。

IDEの助けを借りよう!

IDEは非常に賢くて、使いこなすことで生産性は上がります。 IDEを使うと、こんな便利なことができる というのを理解してもらえればと思います。

適切に改行を入れて、何が起きているかわかりやすくする

ちなみに、先程のソースコードで1処理ごとに改行を入れているのは理由があります。IntelliJでこのソースコードを見ると、各行でなんの型になっているのか表示してくれます。何が起こっているのかわかりやすくなります。

型を推論してもらう

IntelliJだと型の推論までやってくれます。いったん何の変数に格納するかは書かないでおいて、いきなりmaybeReviews.orElse()...から書き始めるとします。

そしてその結果を格納する変数の型を適当にStringとします。

ここで右下の赤い💡をクリックして、「Change variable xxx type to yyy」をクリックします。

自動的にList<String>と型を定義してくれます。

型をわざわざ自分で書かなくて済むので、本質的な作業に集中できます。なので 型は後でIDEに書いてもらう くらい気軽に処理を書き始めても良いのです。

おわりに

ペアプロはけっこう奥深く、教える側にもメリットがあると思います。私は教えているうちに、自分の頭の中にあるJavaの知識が整理されていくのを感じました。ペアプロやってよかったです。