Edited at

Java 8のStream処理はここまで省略できる!

ご存知のように、Java 8で追加されたStreamによって、Javaにおいても関数型言語ライクな処理が書けるようになりました(2014年のリリースから記事執筆時点で3年以上が経過しており、いまさらではありますが……)。 1

せっかくコードを簡潔に書ける仕組みがあるのですから、使わない手はありません。

ここでは、List<String>List<Integer>に変換する単純な処理を行なうコードを徐々に省略していき、その違いを示します。


はじめに

以下の用語については既に理解しているものとし、ここでの説明は行ないません。


  • Stream

  • 関数型インターフェース

  • ラムダ式

  • メソッド参照

手っ取り早く理解したいのであれば、以下の記事がオススメです。


Java 7以前

まずは、Java 7以前のレガシーなコードで書いてみます。


拡張for文を使用

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = new ArrayList<>();
for(final String numText : numTextList){
if(numText == null){
continue;
}
final int num = Integer.parseInt(numText);
numList.add(num);
}

処理部:8行/149文字

拡張for文は通常のfor文と比べてモダンではあるものの、要素の型を書かなければならないのがイケてません。

また、numListを空のリストで初期化している点や、繰り返し処理の中で外部の変数に副作用を与えている点も危ういです。


Java 8以降

ここからは、Java 8以降のモダンなコードで書いてみます。


Streamを使用

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter((String numText) -> {
return Objects.nonNull(numText);
})
.map((String numText) -> {
return Integer.parseInt(numText);
})
.collect(Collectors.toList());

処理部:8行/197文字

Streamを使うことで、numListを直接つくれるようになりました。

ただしこれはかなり冗長な書き方をしており、Streamの良さを活かせているとは言えません。


引数の型名を省略

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter((numText) -> {
return Objects.nonNull(numText);
})
.map((numText) -> {
return Integer.parseInt(numText);
})
.collect(Collectors.toList());

処理部:8行/183文字

ラムダ式では、型推論によって推論可能な型名を省略できます。


引数部の丸括弧を省略

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter(numText -> {
return Objects.nonNull(numText);
})
.map(numText -> {
return Integer.parseInt(numText);
})
.collect(Collectors.toList());

処理部:8行/179文字

ラムダ式では、引数が1個の場合に引数部を囲む丸括弧を省略できます。


処理部の波括弧を省略

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter(numText -> Objects.nonNull(numText))
.map(numText -> Integer.parseInt(numText))
.collect(Collectors.toList());

処理部:4行/145文字

ラムダ式では、処理部が1行で書ける場合に波括弧とreturn文を省略できます。

ここに来てようやく、Java 7以前の書き方より文字数が少なくなりました。

各処理を1行で書くと、メソッドチェーンのつながりも分かりやすいですね。


変数名を短縮

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter(s -> Objects.nonNull(s))
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());

処理部:4行/121文字

ラムダ式の変数はその場でしか使用しないため、変数名を1文字で表すことがあります。

その場合の文字は、型名の頭文字や本来の変数名の頭文字を使うと分かりやすいでしょう。

この形式がわりとよく見る書き方だと思いますが、実はまだ省略可能です。


メソッド参照を使用

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream()
.filter(Objects::nonNull)
.map(Integer::parseInt)
.collect(Collectors.toList());

処理部:4行/107文字

引数のシグニチャ(型とその順番)が一致する場合、ラムダ式のかわりにメソッド参照が使えます。

コードが簡潔になったことで、処理の流れも明確になったのではないでしょうか。


ワンライナー

final List<String> numTextList = Arrays.asList("0", "1", null);

final List<Integer> numList = numTextList.stream().filter(Objects::nonNull).map(Integer::parseInt).collect(Collectors.toList());

処理部:1行/107文字

メソッドチェーンなので、もちろん1行で書けます。


おわりに

あまり省略しすぎると、かえって分かりづらくなることもあるかと思います。

僕の場合、基本的にはラムダ式で書きつつ、メソッド参照が有用な場合(Objects::nonNullPath::toFileなど)はそちらを使うようにしています。

いずれにせよ、可読性の高いコードを書くように心がけたいものですね。


参考リンク