読者です 読者をやめる 読者になる 読者になる

White Box技術部

WEB開発のあれこれ(と何か)

【Java 8】突然のfor文禁止に泣かないためのlambda練習(文字列長の比較)

for文禁止かlambda禁止か

Java 8を利用することになると、恐らくこの話が出てくるのではないかと思います。 というのもfor文で書くコードは、基本lambdaでも書けるわけで、 そうなるとコーディング規約としてはどちらかに寄せないといけない、みたいなところがあるからですね。

lambdaで書けば並行処理の観点からも有利なので、Java 8を使うのであれば 「(可能な限り)for文禁止」というルールにするのが良いと思うのですが・・・

lambdaは今までのJavaとは考え方を変えないと使えないので、 学習コストやなんやらでプロジェクト内の抵抗に合うことも多いのではないでしょうか。

かく言う私も規約作っているチームに話したら「その場合はlambda禁止ですね」と言われた口ですが。

それなら練習すればいいじゃない(マリー

新しいこと・わからないことはご褒美です!少しずつやっていきましょう。

手始めに、for文を書いていて「これラムダでどう書くんだっけ?」と思った処理をlambdaにしてみます。

文字列長が一番多い文字列を返却する方法

『Listを貰って、その中で一番文字数の多い文字列を返すプログラム』を それぞれfor文とlambdaで書いてみました。

for文の例

以下のfor文では、文字列を順々にチェックしていき、 保持しておいた最大長の文字列を返却することで、要求を実現しています。

public String getMaxStringJava7(List<String> list) {

    String result = "";
    for (String str : list) {
        if (str != null && str.length() >= result.length()) {
            result = str;
        }
    }
    return result;
}
lambdaの例

一方、以下のlambdaでは、リストをStreamに変換し(stream)、その中でnullのものを除外し(filter)、 残った要素を逐次的に文字列長比較して(reduce)、結果を返しています(get)。

なんと処理は1文です(その割には長く見えますが)。

public String getMaxStringJava8(List<String> list) {

    return list
            .stream()
            .filter(str -> str != null)
            .reduce((str1, str2) -> str1.length() >= str2.length() ? str1 : str2)
            .get();
}

気付いてますか?その裏仕様

プログラムを書くと、実現したい仕様以外の処理も書かなければいけないことがあります。

例えばこれらのメソッドに、以下のlistを渡した場合、

List<String> list = Arrays.asList("hoge", "hage", "huga", "foo", "1234", null, "hogehoge", "?");

どちらも"hogehoge"が得られます。

ですが、ここで空のリストや、サイズ0のリストを渡した場合はどうでしょう?

空のリストの場合

getMaxStringJava7とgetMaxStringJava8、どちらもNullPointerExceptionです。 これがいいかどうかはさておき、ここの動作は一緒ですね。 (防ぐのであれば、どちらもnullチェックを処理の先頭に入れればいいですし)

サイズ0のリストの場合

問題はこちらです。getMaxStringJava7はresultの初期値である空文字が返却されますが、 getMaxStringJava8はNoSuchElementExceptionが発生します。

これはOptional#get()の仕様で、呼び出し元のOptional型に値が存在しなければ、 NoSuchElementExceptionが発生するようになっています。

「さあ困った。NoSuchElementExceptionをcatchすれば空文字を返すか? いやいやそれならfor文でいいじゃないか。」

そんな心配は不要です。ちょっとだけさっきのコードを変えてみましょう。

lambdaの例(最終形)

最後のget(値があれば出力)をorElse(値があればそれを返し、ない場合は指定の動作をする)に変更しました。

public String getMaxStringJava8(List<String> list) {

    return list
            .stream()
            .filter(str -> str != null)
            .reduce((str1, str2) -> str1.length() >= str2.length() ? str1 : str2)
            .orElse("");
}

新しいJavaを書こう

実はこれが実現していることはすごくて、 例えば「やっぱりリストが空なら0って文字列を返したい」という場合でも、 最後のorElseを.orElse("0");に書き換えるだけで要求を実現できます。

これがfor文の例だと、『リストサイズをチェックして、0の場合は"0"を返す』という処理を別途書く必要が出てきます。

誤解を恐れずに言うと、少ないコード、見通しの良いコードはそれだけで堅牢です。 プログラマの意思をはっきりとコードから感じることができるため、 他の人によるコード改修時も、バグを生じにさせにくくなる、と信じています。

ただ、やはり学習コストの面では、負の部分も見えますね。 まずはStream関係のメソッドにどんなものがあるかを覚えていかないと、 使いこなしているfor文に逃げたくなる場合もあると思います。

でもこれ、アノテーションによるコーディングができるようになったときも言われましたよね? そしてその結果、アノテーションは禁止されたかというと・・・そんなことはないですよね。 確かに最初は覚えてないので難しく感じますけど、アノテーションを覚えたらコード理解はよくなりませんでした?

だからきっと大丈夫ですよ。lambdaでコードを書いていきましょう!